[英]How do I use custom keys with Swift 4's Decodable protocol?
Swift 4 introduced support for native JSON encoding and decoding via theDecodable
protocol. Swift 4 通过可解码协议引入了对原生 JSON 编码和解码的Decodable
。 How do I use custom keys for this?我如何为此使用自定义键?
Eg, say I have a struct例如,说我有一个结构
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
I can encode this to JSON.我可以将其编码为 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"
}
}
}
I can encode this back to an object.我可以将其编码回一个对象。
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
But If I had a json object that was但是如果我有一个 json 对象
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
How would I tell the decoder on Address
that zip_code
maps to zip
?我如何告诉解码器zip_code
映射到zip
Address
? I believe you use the new CodingKey
protocol, but I can't figure out how to use this.我相信你使用新的CodingKey
协议,但我不知道如何使用它。
In your example, you're getting an auto-generated conformance to Codable
as all your properties also conform to Codable
.在您的示例中,您将获得自动生成的Codable
一致性,因为您的所有属性也符合Codable
。 This conformance automatically creates a key type that simply corresponds to the property names – which is then used in order to encode to/decode from a single keyed container.这种一致性会自动创建一个与属性名称简单对应的密钥类型——然后使用它来编码/解码单个密钥容器。
However one really neat feature of this auto-generated conformance is that if you define a nested enum
in your type called " CodingKeys
" (or use a typealias
with this name) that conforms to theCodingKey
protocol – Swift will automatically use this as the key type.但是,这种自动生成的一致性一个真正实用的功能是,如果你定义一个嵌套enum
在您的类型,称为“ CodingKeys
”(或使用typealias
使用该名称),一个符合CodingKey
协议-斯威夫特将自动使用这个作为重点类型。 This therefore allows you to easily customise the keys that your properties are encoded/decoded with.因此,这允许您轻松自定义用于编码/解码属性的密钥。
So what this means is you can just say:所以这意味着你可以说:
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
}
}
The enum case names need to match the property names, and the raw values of these cases need to match the keys that you're encoding to/decoding from (unless specified otherwise, the raw values of a String
enumeration will the same as the case names).枚举 case 名称需要匹配属性名称,并且这些 case 的原始值需要匹配您编码/解码的键(除非另有说明,否则String
枚举的原始值将与 case 相同名称)。 Therefore, the zip
property will now be encoded/decoded using the key "zip_code"
.因此,现在将使用密钥"zip_code"
对zip
属性进行编码/解码。
The exact rules for the auto-generatedEncodable
/Decodable
conformance are detailed by the evolution proposal (emphasis mine):自动生成的可Encodable
/可Decodable
一致性的确切规则在进化提案中详细说明(重点是我的):
In addition to automatic
CodingKey
requirement synthesis forenums
,Encodable
&Decodable
requirements can be automatically synthesized for certain types as well:除了enums
自动CodingKey
需求合成之外,Encodable
以为某些类型自动合成可Encodable
和可Decodable
需求:
Types conforming to
Encodable
whose properties are allEncodable
get an automatically generatedString
-backedCodingKey
enum mapping properties to case names.类型符合Encodable
,其性质都是Encodable
得到一个自动生成的String
-backedCodingKey
枚举映射属性案例名称。 Similarly forDecodable
types whose properties are allDecodable
同样对于所有属性都是Decodable
Decodable
类型Types falling into (1) — and types which manually provide a
CodingKey
enum
(namedCodingKeys
, directly, or via atypealias
) whose cases map 1-to-1 toEncodable
/Decodable
properties by name — get automatic synthesis ofinit(from:)
andencode(to:)
as appropriate, using those properties and keys属于 (1) 的类型-以及手动提供CodingKey
enum
(直接命名为CodingKeys
,或通过typealias
)的类型,其情况按名称将 1 对 1 映射到可Encodable
/ 可Decodable
属性- 自动合成init(from:)
并适当地encode(to:)
,使用这些属性和键Types which fall into neither (1) nor (2) will have to provide a custom key type if needed and provide their own
init(from:)
andencode(to:)
, as appropriate如果需要,既不属于 (1) 也不属于 (2) 的类型必须提供自定义键类型,并根据需要提供自己的init(from:)
和encode(to:)
Example encoding:示例编码:
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"}
Example decoding:示例解码:
// 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 keys for camelCase
property names snake_case
camelCase
属性名称的自动snake_case
JSON键In Swift 4.1, if you rename your zip
property to zipCode
, you can take advantage of the key encoding/decoding strategies on JSONEncoder
and JSONDecoder
in order to automatically convert coding keys between camelCase
and snake_case
.在 Swift 4.1 中,如果您将zip
属性重命名为zipCode
,则可以利用JSONEncoder
和JSONDecoder
上的关键编码/解码策略,以便在camelCase
和snake_case
之间自动转换编码键。
Example encoding:示例编码:
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"}
Example decoding:示例解码:
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")
One important thing to note about this strategy however is that it won't be able to round-trip some property names with acronyms or initialisms which, according to the Swift API design guidelines , should be uniformly upper or lower case (depending on the position).然而,关于此策略需要注意的一件重要事情是,它无法使用首字母缩略词或首字母缩写词来回传递某些属性名称,根据Swift API 设计指南,这些名称应统一为大写或小写(取决于位置) )。
For example, a property named someURL
will be encoded with the key some_url
, but on decoding, this will be transformed to someUrl
.例如,名为someURL
的属性将使用密钥some_url
进行编码,但在解码时,这将转换为someUrl
。
To fix this, you'll have to manually specify the coding key for that property to be string that the decoder expects, eg someUrl
in this case (which will still be transformed to some_url
by the encoder):要解决此问题,您必须手动将该属性的编码键指定为解码器期望的字符串,例如在这种情况下的someUrl
(它仍将被编码器转换为some_url
):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(This doesn't strictly answer your specific question, but given the canonical nature of this Q&A, I feel it's worth including) (这并不能严格回答您的具体问题,但鉴于此问答的规范性质,我觉得值得包括在内)
In Swift 4.1, you can take advantage of the custom key encoding/decoding strategies on JSONEncoder
and JSONDecoder
, allowing you to provide a custom function to map coding keys.在 Swift 4.1 中,您可以利用JSONEncoder
和JSONDecoder
上的自定义键编码/解码策略,允许您提供自定义函数来映射编码键。
The function you provide takes a [CodingKey]
, which represents the coding path for the current point in encoding/decoding (in most cases, you'll only need to consider the last element; that is, the current key).您提供的函数采用[CodingKey]
,它表示编码/解码中当前点的编码路径(在大多数情况下,您只需要考虑最后一个元素;即当前键)。 The function returns a CodingKey
that will replace the last key in this array.该函数返回一个CodingKey
,它将替换此数组中的最后一个键。
For example, UpperCamelCase
JSON keys for lowerCamelCase
property names:例如, lowerCamelCase
属性名称的UpperCamelCase
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
}
}
}
You can now encode with the .convertToUpperCamelCase
key strategy:您现在可以使用.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"}
and decode with the .convertFromUpperCamelCase
key strategy:并使用.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")
With Swift 4.2, according to your needs, you may use one of the 3 following strategies in order to make your model objects custom property names match your JSON keys.使用 Swift 4.2,根据您的需要,您可以使用以下 3 种策略之一,以使您的模型对象自定义属性名称与您的 JSON 键匹配。
When you declare a struct that conforms to Codable
( Decodable
and Encodable
protocols) with the following implementation...当您使用以下实现声明符合Codable
(可Decodable
和可Encodable
协议)的结构时...
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
}
... the compiler automatically generates a nested enum that conforms to CodingKey
protocol for you. ...编译器会自动为您生成符合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
}
}
Therefore, if the keys used in your serialized data format don't match the property names from your data type, you can manually implement this enum and set the appropriate rawValue
for the required cases.因此,如果序列化数据格式中使用的键与数据类型中的属性名称不匹配,您可以手动实现此枚举并为所需情况设置适当的rawValue
。
The following example shows how to do:下面的例子展示了如何做:
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
}
}
Encode (replacing zip
property with "zip_code" JSON key):编码(用“zip_code”JSON 密钥替换zip
属性):
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"}
*/
Decode (replacing "zip_code" JSON key with zip
property):解码(用zip
属性替换“zip_code”JSON 密钥):
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")
*/
If your JSON has snake-cased keys and you want to convert them to camel-cased properties for your model object, you can set your JSONEncoder
's keyEncodingStrategy
and JSONDecoder
's keyDecodingStrategy
properties to .convertToSnakeCase
.如果您的 JSON 具有蛇形键,并且您想将它们转换为模型对象的骆驼形属性,则可以将JSONEncoder
的keyEncodingStrategy
和JSONDecoder
的keyDecodingStrategy
属性设置为.convertToSnakeCase
。
The following example shows how to do:下面的例子展示了如何做:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
Encode (converting camel cased properties into snake cased JSON keys):编码(将骆驼壳属性转换为蛇壳 JSON 密钥):
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"}
*/
Decode (converting snake cased JSON keys into camel cased properties):解码(将蛇型 JSON 密钥转换为骆驼型属性):
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")
*/
If necessary, JSONEncoder
and JSONDecoder
allow you to set a custom strategy to map coding keys using JSONEncoder.KeyEncodingStrategy.custom(_:)
and JSONDecoder.KeyDecodingStrategy.custom(_:)
.如有必要, JSONEncoder
和JSONDecoder
允许您设置自定义策略以使用JSONEncoder.KeyEncodingStrategy.custom(_:)
和JSONDecoder.KeyDecodingStrategy.custom(_:)
映射编码键。
The following example shows how to implement them:以下示例显示了如何实现它们:
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
}
}
Encode (converting lowercased first letter properties into uppercased first letter JSON keys):编码(将小写首字母属性转换为大写首字母 JSON 键):
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"}
*/
Decode (converting uppercased first letter JSON keys into lowercased first letter properties):解码(将大写首字母 JSON 键转换为小写首字母属性):
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")
*/
Sources:资料来源:
What I have done is create own structure just like what you are getting from the JSON with respect to its data types.我所做的是创建自己的结构,就像您从 JSON 中获得的数据类型一样。
Just like this:像这样:
struct Track {
let id : Int
let contributingArtistNames:String
let name : String
let albumName :String
let copyrightP:String
let copyrightC:String
let playlistCount:Int
let trackPopularity:Int
let playlistFollowerCount:Int
let artistFollowerCount : Int
let label : String
}
After this you need to create an extension of the same struct
extending decodable
and the enum
of the same structure with CodingKey
and then you need to initialize the decoder using this enum with its keys and datatypes (Keys will come from the enum and the datatypes will be coming or say referenced from the structure itself)在此之后,您需要使用CodingKey
创建扩展decodable
的相同struct
扩展和相同struct
的enum
,然后您需要使用此枚举及其键和数据类型来初始化解码器(键将来自枚举,数据类型将来自或说从结构本身引用)
extension Track: Decodable {
enum TrackCodingKeys: String, CodingKey {
case id = "id"
case contributingArtistNames = "primaryArtistsNames"
case spotifyId = "spotifyId"
case name = "name"
case albumName = "albumName"
case albumImageUrl = "albumImageUrl"
case copyrightP = "copyrightP"
case copyrightC = "copyrightC"
case playlistCount = "playlistCount"
case trackPopularity = "trackPopularity"
case playlistFollowerCount = "playlistFollowerCount"
case artistFollowerCount = "artistFollowers"
case label = "label"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.id){
id = try trackContainer.decode(Int.self, forKey: .id)
}else{
id = 0
}
if trackContainer.contains(.contributingArtistNames){
contributingArtistNames = try trackContainer.decode(String.self, forKey: .contributingArtistNames)
}else{
contributingArtistNames = ""
}
if trackContainer.contains(.spotifyId){
spotifyId = try trackContainer.decode(String.self, forKey: .spotifyId)
}else{
spotifyId = ""
}
if trackContainer.contains(.name){
name = try trackContainer.decode(String.self, forKey: .name)
}else{
name = ""
}
if trackContainer.contains(.albumName){
albumName = try trackContainer.decode(String.self, forKey: .albumName)
}else{
albumName = ""
}
if trackContainer.contains(.albumImageUrl){
albumImageUrl = try trackContainer.decode(String.self, forKey: .albumImageUrl)
}else{
albumImageUrl = ""
}
if trackContainer.contains(.copyrightP){
copyrightP = try trackContainer.decode(String.self, forKey: .copyrightP)
}else{
copyrightP = ""
}
if trackContainer.contains(.copyrightC){
copyrightC = try trackContainer.decode(String.self, forKey: .copyrightC)
}else{
copyrightC = ""
}
if trackContainer.contains(.playlistCount){
playlistCount = try trackContainer.decode(Int.self, forKey: .playlistCount)
}else{
playlistCount = 0
}
if trackContainer.contains(.trackPopularity){
trackPopularity = try trackContainer.decode(Int.self, forKey: .trackPopularity)
}else{
trackPopularity = 0
}
if trackContainer.contains(.playlistFollowerCount){
playlistFollowerCount = try trackContainer.decode(Int.self, forKey: .playlistFollowerCount)
}else{
playlistFollowerCount = 0
}
if trackContainer.contains(.artistFollowerCount){
artistFollowerCount = try trackContainer.decode(Int.self, forKey: .artistFollowerCount)
}else{
artistFollowerCount = 0
}
if trackContainer.contains(.label){
label = try trackContainer.decode(String.self, forKey: .label)
}else{
label = ""
}
}
}
You need to change here each and every key and datatypes according to your needs and use it with the decoder.您需要根据需要在此处更改每个键和数据类型,并将其与解码器一起使用。
By using CodingKey you can use custom keys in codable or decodable protocol.通过使用CodingKey,您可以在可编码或可解码协议中使用自定义密钥。
struct person: Codable {
var name: String
var age: Int
var street: String
var state: String
private enum CodingKeys: String, CodingKey {
case name
case age
case street = "Street_name"
case state
} }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.