Настройка ключей кодирования вручную
В вашем примере вы получаете автоматически сгенерированное соответствие, 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
-backed CodingKey
перечислений свойства отображения имен в случае. Аналогично для Decodable
типов, все свойства которыхDecodable
Типы попадающих в (1) - и типах , которые вручную обеспечивают CodingKey
enum
( по имени CodingKeys
, непосредственно, или через typealias
) , чьи дела на карту 1-к-1 к Encodable
/ Decodable
свойств по названию - получить автоматический синтез 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
имен свойств
В Swift 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
}
(Это не совсем ответ на ваш конкретный вопрос, но, учитывая канонический характер этого вопроса и ответа, я считаю, что его стоит включить)
Пользовательское автоматическое сопоставление ключей JSON
В Swift 4.1 вы можете воспользоваться преимуществами настраиваемых стратегий кодирования / декодирования ключей на JSONEncoder
и JSONDecoder
, что позволяет вам предоставить настраиваемую функцию для сопоставления ключей кодирования.
Предоставляемая вами функция принимает значение [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")
CodingKeys
enum; могу я просто перечислить один ключ, который я меняю?