Swift 4通过该Decodable协议引入了对本机JSON编码和解码的支持。如何为此使用自定义键?
Decodable
例如,说我有一个结构
struct Address:Codable { var street:String var zip:String var city:String var state:String }
我可以将其编码为JSON。
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") if let encoded = try? encoder.encode(address) { if let json = String(data: encoded, encoding: .utf8) { // Print JSON String print(json) // JSON string is { "state":"California", "street":"Apple Bay Street", "zip":"94608", "city":"Emeryville" } } }
我可以将此编码回一个对象。
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
但是如果我有一个json对象
{ "state":"California", "street":"Apple Bay Street", "zip_code":"94608", "city":"Emeryville" }
我怎么会告诉解码器上Address是zip_code映射到zip?我相信您使用的是新CodingKey协议,但是我不知道该如何使用。
Address
zip_code
zip
CodingKey
在您的示例中,Codable由于所有属性也都符合,因此您将获得自动生成的符合Codable。这种一致性会自动创建一个仅与属性名称相对应的密钥类型,然后将其用于对单个密钥容器进行编码/解码。
Codable
但是,这种自动生成的一致性的一个 真正 巧妙的功能是,如果您enum在称为“ CodingKeys” 的类型中定义一个嵌套(或使用typealias具有此名称的a)来符合CodingKey协议,则Swift将自动将 其 用作键类型。因此,这使您可以轻松自定义用于对属性进行编码/解码的键。
enum
CodingKeys
typealias
因此,这意味着您可以说:
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 } }
枚举案例名称需要与属性名称匹配,并且这些案例的原始值需要与您要编码/解码的键匹配(除非另有指定,否则String枚举的原始值将与案例名称相同)。因此,zip现在将使用key 对属性进行编码/解码"zip_code"。
String
"zip_code"
有关自动生成Encodable/ Decodable一致性的确切规则,请参见演进提案(重点是我的):
Encodable
除了的自动CodingKey需求综合外 enums,Encodable&Decodable需求还可以针对某些类型自动综合: 1. 符合Encodable所有属性的类型将Encodable获得自动生成的String支持的CodingKey枚举,将枚举属性映射到案例名称。对于Decodable所有属性都相同的类型也是如此Decodable 属于(1)的 类型CodingKey enum``CodingKeys``typealias``Encodable``Decodable - 以及手动提供类型 (名称为 ,直接或通过a )将其案例 按名称 一对一映射到 / 属性的类型 - 使用这些属性和键自动进行合并,init(from:)并encode(to:)在适当时进行合并 落入类型既不(1)也没有(2)将具有如果需要的话提供一个自定义的密钥类型和提供它们自己的init(from:)和 encode(to:),如适当
除了的自动CodingKey需求综合外 enums,Encodable&Decodable需求还可以针对某些类型自动综合:
enums
1. 符合Encodable所有属性的类型将Encodable获得自动生成的String支持的CodingKey枚举,将枚举属性映射到案例名称。对于Decodable所有属性都相同的类型也是如此Decodable
属于(1)的 类型CodingKey enum``CodingKeys``typealias``Encodable``Decodable - 以及手动提供类型 (名称为 ,直接或通过a )将其案例 按名称 一对一映射到 / 属性的类型 - 使用这些属性和键自动进行合并,init(from:)并encode(to:)在适当时进行合并
enum``CodingKeys``typealias``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_case
camelCase
在雨燕4.1,如果您重命名zip属性zipCode,你可以利用关键的编码/解码上的策略JSONEncoder,并JSONDecoder以自动转换之间的编码键camelCase和snake_case。
zipCode
JSONEncoder
JSONDecoder
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设计指南,这些属性名称应统一使用大写或小写(取决于位置) )。
例如,名为的属性someURL将使用键进行编码some_url,但是在解码时,它将转换为someUrl。
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 }
(这并不能严格回答您的特定问题,但是鉴于此问答的典型性质,我认为值得考虑)
在雨燕4.1,您可以利用自定义按键编码/解码上的策略JSONEncoder和JSONDecoder,使您可以提供一个自定义函数映射的编码键。
您提供的函数采用[CodingKey],代表编码/解码中当前点的编码路径(在大多数情况下,您只需要考虑最后一个元素;即当前键)。该函数返回一个CodingKey,它将替换此数组中的最后一个键。
[CodingKey]
例如,属性名称的UpperCamelCaseJSON键lowerCamelCase:
UpperCamelCase
lowerCamelCase
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关键策略进行编码:
.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关键策略进行解码:
.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")