Настройка ключей кодирования вручную
В вашем примере вы получаете автоматически сгенерированное соответствие, 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")
CodingKeysenum; могу я просто перечислить один ключ, который я меняю?