手动自定义编码键
在您的示例中,您的所有属性都符合Codable
,因此自动生成的一致性为Codable
.这种一致性会自动创建一个只与属性名相对应的密钥类型,然后使用该类型对单个密钥容器进行编码/解码.
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 the CodingKey
protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.
这意味着你可以说:
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
枚举的原始值将与 case 名称相同).因此,现在将使用密钥"zip_code"
对zip
属性进行编码/解码.
The exact rules for the auto-generated Encodable
/Decodable
conformance are detailed by the evolution proposal (emphasis mine):
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
如果需要,既不属于(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")
Automatic snake_case
JSON keys for camelCase
property names
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
.
编码示例:
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")
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).
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
):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(这并没有严格地回答您的具体问题,但考虑到本问答的规范性,我觉得它值得包括在内)
自定义自动JSON密钥映射
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.
你所提供的函数取一个[CodingKey]
,它代表编码/解码中当前点的编码路径(在大多数情况下,你只需要考虑最后一个元素,即,当前密钥).该函数返回一个CodingKey
,它将替换该数组中的最后一个键.
For example, UpperCamelCase
JSON keys for lowerCamelCase
property 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")