手动自定义编码键
在您的示例中,您将自动生成符合Codable,因为您的所有属性也符合Codable。这种一致性会自动创建一个与属性名称相对应的键类型,然后使用该类型对单个键容器进行编码/解码。
但是,这种自动生成的一致性的一个真正巧妙的特性是,如果您在名为“CodingKeys”的类型中定义一个嵌套的enum(或使用具有此名称的typealias ) 符合CodingKey 协议 - Swift 将自动使用 this 作为键类型。因此,您可以轻松自定义用于编码/解码属性的键。
所以这意味着你可以说:
struct Address : Codable {
var street: String
var zip: String
var city: String
var state: String
private enum CodingKeys : String, CodingKey {
case street, zip = "zip_code", city, state
}
}
枚举 case 名称需要匹配属性名称,并且这些 case 的原始值需要匹配您编码/解码的键(除非另有说明,String 枚举的原始值将与案例名称相同)。因此,zip 属性现在将使用密钥 "zip_code" 进行编码/解码。
the evolution proposal 详细说明了自动生成的 Encodable/Decodable 一致性的确切规则(强调我的):
除了自动CodingKey 需求综合
enums, Encodable & Decodable 要求可以自动
也为某些类型合成:
符合Encodable且属性全部为Encodable的类型获取自动生成的String支持的CodingKey枚举映射
案例名称的属性。类似的Decodable 类型
属性都是Decodable
属于 (1) 的类型 — 以及手动提供 CodingKey enum(直接命名为 CodingKeys,或通过 typealias)的类型,其
case 按名称一对一映射到Encodable/Decodable 属性 — 获取
视情况自动合成init(from:) 和encode(to:),
使用这些属性和键
既不属于 (1) 也不属于 (2) 的类型必须在需要时提供自定义键类型并提供自己的 init(from:) 和
encode(to:),视情况而定
示例编码:
import Foundation
let address = Address(street: "Apple Bay Street", zip: "94608",
city: "Emeryville", state: "California")
do {
let encoded = try JSONEncoder().encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
解码示例:
// using the """ multi-line string literal here, as introduced in SE-0168,
// to avoid escaping the quotation marks
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""
do {
let decoded = try JSONDecoder().decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zip: "94608",
// city: "Emeryville", state: "California")
snake_casecamelCase 属性名称的自动 snake_case JSON 键
在 Swift 4.1 中,如果将 zip 属性重命名为 zipCode,则可以利用 JSONEncoder 和 JSONDecoder 上的密钥编码/解码策略,以便在 camelCase 之间自动转换编码密钥和snake_case。
示例编码:
import Foundation
struct Address : Codable {
var street: String
var zipCode: String
var city: String
var state: String
}
let address = Address(street: "Apple Bay Street", zipCode: "94608",
city: "Emeryville", state: "California")
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let encoded = try encoder.encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
解码示例:
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")
然而,关于此策略需要注意的重要一点是,它无法使用首字母缩略词或首字母缩写来往返某些属性名称,根据Swift API design guidelines,它们应该是统一的大写或小写(取决于位置)。
例如,一个名为someURL 的属性将使用密钥some_url 进行编码,但在解码时,它将转换为someUrl。
要解决此问题,您必须手动将该属性的编码键指定为解码器期望的字符串,例如在这种情况下为someUrl(编码器仍将其转换为some_url):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(这并没有严格回答您的具体问题,但鉴于此问答的规范性质,我觉得值得包括在内)
自定义自动 JSON 键映射
在 Swift 4.1 中,您可以利用 JSONEncoder 和 JSONDecoder 上的自定义键编码/解码策略,允许您提供自定义函数来映射编码键。
您提供的函数采用[CodingKey],它表示编码/解码中当前点的编码路径(在大多数情况下,您只需要考虑最后一个元素;即当前键)。该函数返回一个CodingKey,它将替换此数组中的最后一个键。
例如,UpperCamelCaselowerCamelCase 属性名称的 JSON 键:
import Foundation
// wrapper to allow us to substitute our mapped string keys.
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ base: CodingKey) {
self.init(stringValue: base.stringValue, intValue: base.intValue)
}
init(stringValue: String) {
self.stringValue = stringValue
}
init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
}
extension JSONEncoder.KeyEncodingStrategy {
static var convertToUpperCamelCase: JSONEncoder.KeyEncodingStrategy {
return .custom { codingKeys in
var key = AnyCodingKey(codingKeys.last!)
// uppercase first letter
if let firstChar = key.stringValue.first {
let i = key.stringValue.startIndex
key.stringValue.replaceSubrange(
i ... i, with: String(firstChar).uppercased()
)
}
return key
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromUpperCamelCase: JSONDecoder.KeyDecodingStrategy {
return .custom { codingKeys in
var key = AnyCodingKey(codingKeys.last!)
// lowercase first letter
if let firstChar = key.stringValue.first {
let i = key.stringValue.startIndex
key.stringValue.replaceSubrange(
i ... i, with: String(firstChar).lowercased()
)
}
return key
}
}
}
您现在可以使用.convertToUpperCamelCase 键策略进行编码:
let address = Address(street: "Apple Bay Street", zipCode: "94608",
city: "Emeryville", state: "California")
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToUpperCamelCase
let encoded = try encoder.encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}
并使用.convertFromUpperCamelCase 密钥策略进行解码:
let jsonString = """
{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromUpperCamelCase
let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")