如何将自定义密钥与 Swift 4 的 Decodable 协议一起使用?
How do I use custom keys with Swift 4's Decodable protocol?
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
。这种一致性会自动创建一个密钥类型,该密钥类型仅对应于 属性 名称 - 然后用于从单个密钥容器中编码 to/decode。
然而,这个自动生成的一致性的一个 真正 巧妙的特点是,如果你在你的类型中定义一个嵌套的 enum
称为“CodingKeys
”(或使用符合 CodingKey
协议的 typealias
名称) – Swift 将自动使用 this 作为密钥类型。因此,这允许您轻松自定义您的属性 encoded/decoded 的键。
这意味着你可以说:
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
}
}
枚举案例名称需要与 属性 名称相匹配,并且这些案例的原始值需要与您从中编码 to/decoding 的键相匹配(除非另有说明,原始值String
枚举的值将与案例名称相同)。因此,zip
属性 现在将是 encoded/decoded 使用键 "zip_code"
.
自动生成的确切规则 Encodable
/Decodable
conformance are detailed by the evolution proposal(强调我的):
In addition to automatic CodingKey
requirement synthesis for
enums
, Encodable
& Decodable
requirements can be automatically
synthesized for certain types as well:
Types conforming to Encodable
whose properties are all Encodable
get an automatically generated String
-backed CodingKey
enum mapping
properties to case names. Similarly for Decodable
types whose
properties are all Decodable
Types falling into (1) — and types which manually provide a CodingKey
enum
(named CodingKeys
, directly, or via a typealias
) whose
cases map 1-to-1 to Encodable
/Decodable
properties by name — get
automatic synthesis of init(from:)
and encode(to:)
as appropriate,
using those properties and keys
Types which fall into neither (1) nor (2) will have to provide a custom key type if needed and provide their own init(from:)
and
encode(to:)
, as appropriate
示例编码:
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
属性 名称
在 Swift 4.1 中,如果您将 zip
属性 重命名为 zipCode
,您可以利用关键的 encoding/decoding 策略 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()
<b>encoder.keyEncodingStrategy = .convertToSnakeCase</b>
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()
<b>decoder.keyDecodingStrategy = .convertFromSnakeCase</b>
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
上的自定义键 encoding/decoding 策略,允许您提供自定义函数来映射编码键。
你提供的函数带一个[CodingKey]
,表示encoding/decoding中当前点的编码路径(大多数情况下,你只需要考虑最后一个元素;即,当前密钥)。函数 returns a CodingKey
将替换此数组中的最后一个键。
例如,UpperCamelCase
JSON keys for lowerCamelCase
属性 names:
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 键匹配。
#1。使用自定义编码键
当您使用以下实现声明符合 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
}
}
编码(用"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"}
*/
解码(用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")
*/
#2。使用蛇形大小写到驼峰大小写关键编码策略
如果您的 JSON 有蛇形键并且您想将它们转换为模型对象的驼峰式属性,您可以设置 JSONEncoder
的 keyEncodingStrategy
and JSONDecoder
's keyDecodingStrategy
属性至 .convertToSnakeCase
.
下面的例子展示了如何做:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
编码(将驼峰式属性转换为蛇式 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"}
*/
解码(将蛇形 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")
*/
#3。使用自定义键编码策略
如有必要,JSONEncoder
和 JSONDecoder
允许您设置自定义策略以使用 JSONEncoder.KeyEncodingStrategy.custom(_:)
and JSONDecoder.KeyDecodingStrategy.custom(_:)
映射编码键。
以下示例显示了如何实现它们:
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
}
}
编码(将小写首字母属性转换为大写首字母 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"}
*/
解码(将大写首字母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")
*/
来源:
我所做的是创建自己的结构,就像您从 JSON 获得的数据类型一样。
就像这样:
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
}
在此之后你需要创建一个扩展 struct
扩展 decodable
和 enum
相同结构的 CodingKey
然后你需要初始化解码器使用这个枚举及其键和数据类型(键将来自枚举,数据类型将来自或说从结构本身引用)
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 = ""
}
}
}
您需要根据需要在此处更改每个键和数据类型,并将其与解码器一起使用。
通过使用 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
} }
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
。这种一致性会自动创建一个密钥类型,该密钥类型仅对应于 属性 名称 - 然后用于从单个密钥容器中编码 to/decode。
然而,这个自动生成的一致性的一个 真正 巧妙的特点是,如果你在你的类型中定义一个嵌套的 enum
称为“CodingKeys
”(或使用符合 CodingKey
协议的 typealias
名称) – Swift 将自动使用 this 作为密钥类型。因此,这允许您轻松自定义您的属性 encoded/decoded 的键。
这意味着你可以说:
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
}
}
枚举案例名称需要与 属性 名称相匹配,并且这些案例的原始值需要与您从中编码 to/decoding 的键相匹配(除非另有说明,原始值String
枚举的值将与案例名称相同)。因此,zip
属性 现在将是 encoded/decoded 使用键 "zip_code"
.
自动生成的确切规则 Encodable
/Decodable
conformance are detailed by the evolution proposal(强调我的):
In addition to automatic
CodingKey
requirement synthesis forenums
,Encodable
&Decodable
requirements can be automatically synthesized for certain types as well:
Types conforming to
Encodable
whose properties are allEncodable
get an automatically generatedString
-backedCodingKey
enum mapping properties to case names. Similarly forDecodable
types whose properties are allDecodable
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 keysTypes 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
示例编码:
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
属性 名称
在 Swift 4.1 中,如果您将 zip
属性 重命名为 zipCode
,您可以利用关键的 encoding/decoding 策略 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()
<b>encoder.keyEncodingStrategy = .convertToSnakeCase</b>
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()
<b>decoder.keyDecodingStrategy = .convertFromSnakeCase</b>
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
上的自定义键 encoding/decoding 策略,允许您提供自定义函数来映射编码键。
你提供的函数带一个[CodingKey]
,表示encoding/decoding中当前点的编码路径(大多数情况下,你只需要考虑最后一个元素;即,当前密钥)。函数 returns a CodingKey
将替换此数组中的最后一个键。
例如,UpperCamelCase
JSON keys for lowerCamelCase
属性 names:
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 键匹配。
#1。使用自定义编码键
当您使用以下实现声明符合 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
}
}
编码(用"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"}
*/
解码(用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")
*/
#2。使用蛇形大小写到驼峰大小写关键编码策略
如果您的 JSON 有蛇形键并且您想将它们转换为模型对象的驼峰式属性,您可以设置 JSONEncoder
的 keyEncodingStrategy
and JSONDecoder
's keyDecodingStrategy
属性至 .convertToSnakeCase
.
下面的例子展示了如何做:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
编码(将驼峰式属性转换为蛇式 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"}
*/
解码(将蛇形 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")
*/
#3。使用自定义键编码策略
如有必要,JSONEncoder
和 JSONDecoder
允许您设置自定义策略以使用 JSONEncoder.KeyEncodingStrategy.custom(_:)
and JSONDecoder.KeyDecodingStrategy.custom(_:)
映射编码键。
以下示例显示了如何实现它们:
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
}
}
编码(将小写首字母属性转换为大写首字母 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"}
*/
解码(将大写首字母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")
*/
来源:
我所做的是创建自己的结构,就像您从 JSON 获得的数据类型一样。
就像这样:
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
}
在此之后你需要创建一个扩展 struct
扩展 decodable
和 enum
相同结构的 CodingKey
然后你需要初始化解码器使用这个枚举及其键和数据类型(键将来自枚举,数据类型将来自或说从结构本身引用)
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 = ""
}
}
}
您需要根据需要在此处更改每个键和数据类型,并将其与解码器一起使用。
通过使用 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
} }