Swift 4通过Decodable
协议引入了对本机JSON编码和解码的支持.我如何使用自定义键?
比如说我有一个结构
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
协议,但我无法弄清楚如何使用它.
在您的示例中,您将获得自动生成的一致性,Codable
因为您的所有属性也符合Codable
.此一致性自动创建一个简单对应于属性名称的密钥类型 - 然后使用该密钥类型从单个密钥容器进行编码/解码.
但是,这种自动生成的一致性的一个非常巧妙的功能是,如果您enum
在类型中定义嵌套,名为" CodingKeys
"(或使用typealias
具有此名称的)符合CodingKey
协议 - Swift将自动将其用作密钥类型.因此,您可以轻松自定义用于编码/解码属性的键.
那么这意味着你可以说:
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
现在将使用密钥对属性进行编码/解码"zip_code"
.
自动生成Encodable
/ Decodable
一致性的确切规则由演化提议详细说明(强调我的):
除了自动
CodingKey
要求合成为enums
,Encodable
&Decodable
要求可对某些类型的自动合成,以及:
类型符合
Encodable
其属性都Encodable
得到一个自动生成的String
-backedCodingKey
枚举映射属性来区分的名称.类似于Decodable
属性都是的类型Decodable
落入(1)的类型
CodingKey
enum
CodingKeys
typealias
Encodable
Decodable
- 以及手动提供(命名,直接或通过a )其案例按名称将1对1映射到/ 属性的类型 - 使用这些属性和键自动合成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
JSON键camelCase
在雨燕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设计指南,它应该是统一的大写或小写(取决于位置) ).
例如,名为的属性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 }
(这并没有严格回答您的具体问题,但考虑到此问答的规范性质,我觉得值得包括)
在Swift 4.1中,您可以利用自定义密钥编码/解码策略,JSONEncoder
并JSONDecoder
允许您提供自定义函数来映射编码密钥.
您提供的函数采用a [CodingKey]
,它表示编码/解码中当前点的编码路径(在大多数情况下,您只需要考虑最后一个元素;即当前密钥).该函数返回一个CodingKey
替换该数组中最后一个键的函数.
例如,属性名称的UpperCamelCase
JSON键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
关键策略进行编码:
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")
使用Swift 4.2,根据您的需要,您可以使用以下3种策略之一,以使您的模型对象自定义属性名称与您的JSON键匹配.
使用以下实现声明符合Codable
(Decodable
和Encodable
协议)的结构时...
struct Address: Codable { var street: String var zip: String var city: String var state: String }
...编译器会自动CodingKey
为您生成符合协议的嵌套枚举.
struct Address: Codable { var street: String var zip: String var city: String var state: String // compiler generated private enum CodingKeys: String, CodingKey { case street case zip case city case state } }
因此,如果序列化数据格式中使用的键与数据类型中的属性名称不匹配,则可以手动实现此枚举并rawValue
为所需的案例设置适当的值.
以下示例显示了如何执行:
import Foundation struct Address: Codable { var street: String var zip: String var city: String var state: String private enum CodingKeys: String, CodingKey { case street case zip = "zip_code" case city case state } }
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") let encoder = JSONEncoder() if let jsOnData= try? encoder.encode(address), let jsOnString= String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} */
let jsOnString= """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} """ let decoder = JSONDecoder() if let jsOnData= jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") */
如果你的JSON有蛇套管键,你想将它们转换为骆驼套管性能为您的模型对象,你可以设置你zip
的zip
和JSONEncoder
的keyEncodingStrategy
属性JSONDecoder
.
以下示例显示了如何执行:
import Foundation struct Address: Codable { var street: String var zipCode: String var cityName: String var state: String }
let address = Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase if let jsOnData= try? encoder.encode(address), let jsOnString= String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"} */
let jsOnString= """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"} """ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase if let jsOnData= jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California") */
如果有必要,keyDecodingStrategy
并.convertToSnakeCase
允许您设置自定义策略来映射使用的编码键JSONEncoder
和JSONDecoder
.
以下示例显示了如何实现它们:
import Foundation struct Address: Codable { var street: String var zip: String var city: String var state: String } struct AnyKey: CodingKey { var stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } }
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .custom({ (keys) -> CodingKey in let lastKey = keys.last! guard lastKey.intValue == nil else { return lastKey } let stringValue = lastKey.stringValue.prefix(1).uppercased() + lastKey.stringValue.dropFirst() return AnyKey(stringValue: stringValue)! }) if let jsOnData= try? encoder.encode(address), let jsOnString= String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"Zip":"94608","Street":"Apple Bay Street","City":"Emeryville","State":"California"} */
let jsOnString= """ {"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"} """ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in let lastKey = keys.last! guard lastKey.intValue == nil else { return lastKey } let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst() return AnyKey(stringValue: stringValue)! }) if let jsOnData= jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") */
资料来源:
Apple开发人员文档:"编码和解码自定义类型"
WWDC 2017年会议212:"基金会有什么新鲜事"
MartianCraft:"为编码类型实现自定义密钥策略"