如何在Swift 4的Decodable协议中使用自定义键? [英] How do I use custom keys with Swift 4's Decodable protocol?
问题描述
Swift 4通过 Decodable
协议引入了对本机JSON编码和解码的支持.如何为此使用自定义键?
Swift 4 introduced support for native JSON encoding and decoding via the Decodable
protocol. How do I use custom keys for this?
例如,说我有一个结构
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
我可以将其编码为JSON.
I can encode this to 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)
但是如果我有一个
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
我如何告诉Address
上的解码器zip_code
映射到zip
?我相信您使用的是新的CodingKey
协议,但是我不知道如何使用它.
How would I tell the decoder on Address
that zip_code
maps to zip
? I believe you use the new CodingKey
protocol, but I can't figure out how to use this.
推荐答案
手动自定义编码键
在您的示例中,您将自动生成与 Codable
,因为您的所有属性也符合Codable
.这种一致性会自动创建一个仅与属性名称相对应的密钥类型,然后将其用于对单个密钥容器进行编码/从中解码.
Manually customising coding keys
In your example, you're getting an auto-generated conformance to Codable
as all your properties also conform to 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.
然而,这种自动生成的符合性的真正整洁特征是,如果您在名为"CodingKeys
"的类型中定义了嵌套的enum
(或使用具有该名称的typealias
),符合 CodingKey
协议– Swift将自动使用 this 作为密钥类型.因此,这使您可以轻松自定义用于对属性进行编码/解码的键.
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.
所以这意味着你可以说:
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
}
}
枚举案例名称需要与属性名称匹配,并且这些案例的原始值需要与您要编码/解码的键匹配(除非另有指定,否则String
枚举的原始值将与与案例名称相同).因此,现在将使用"zip_code"
键对zip
属性进行编码/解码.
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). Therefore, the zip
property will now be encoded/decoded using the key "zip_code"
.
自动生成的 Encodable
/
The exact rules for the auto-generated Encodable
/Decodable
conformance are detailed by the evolution proposal (emphasis mine):
除了自动的
CodingKey
需求综合enums
,Encodable
&Decodable
要求可以自动 也可以针对某些类型进行合成:
In addition to automatic
CodingKey
requirement synthesis forenums
,Encodable
&Decodable
requirements can be automatically synthesized for certain types as well:
-
所有属性均符合
Encodable
的Encodable
兼容类型将自动获得String
支持的CodingKey
枚举映射 案例名称的属性.对于Decodable
类型,其 属性都是Decodable
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
属于(1)— 的类型,以及手动提供CodingKey
enum
(直接或通过typealias
命名为CodingKeys
)的类型
例按名称将一对一映射到Encodable
/Decodable
属性-获取
自动合成init(from:)
和encode(to:)
,
使用这些属性和键
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
类型必须提供一个自定义键类型(如果需要),并提供自己的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:)
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")
用于camelCase
属性名称的自动snake_case
JSON键
在Swift 4.1中,如果将zip
属性重命名为zipCode
,则可以利用JSONEncoder
和JSONDecoder
上的密钥编码/解码策略,以便在camelCase
之间自动转换编码密钥.和snake_case
.
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).
例如,名为someURL
的属性将使用键some_url
进行编码,但是在解码时,它将被转换为someUrl
.
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
):
To fix this, you'll have to manually specify the coding key for that property to be string that the decoder expects, e.g someUrl
in this case (which will still be transformed to some_url
by the encoder):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(这不能严格回答您的特定问题,但是鉴于此问题与解答的规范性质,我觉得值得包括在内)
在Swift 4.1中,您可以利用JSONEncoder
和JSONDecoder
上的自定义密钥编码/解码策略,从而允许您提供自定义函数来映射编码密钥.
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
,它将替换该数组中的最后一个键.
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). The function returns a CodingKey
that will replace the last key in this array.
例如,lowerCamelCase
属性名称的UpperCamelCase
JSON键:
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
密钥策略进行编码:
You can now encode with the .convertToUpperCamelCase
key strategy:
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
密钥策略进行解码:
and decode with the .convertFromUpperCamelCase
key strategy:
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的Decodable协议中使用自定义键?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!