Налаштування ключів кодування вручну
У вашому прикладі ви отримуєте автоматично створену відповідність тому, Codableяк відповідають і всі ваші властивості Codable. Ця відповідність автоматично створює тип ключа, який просто відповідає іменам властивостей - який потім використовується для кодування та декодування з одного контейнера з ключем.
Однак одна з дійсно акуратних властивостей цього автоматично сформованого відповідності полягає в тому, що якщо ви визначите вкладений enumу своєму типі під назвою " CodingKeys" (або використовуєте a 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захищені переліку переліку CodingKeyвластивості перерахування до імен справ. Аналогічно для Decodableтипів, властивості яких усіDecodable
Типи, що потрапляють у (1) - та типи, які надають вручну CodingKey enum(названі CodingKeys, безпосередньо чи через a 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, що дозволяють вам надати спеціальну функцію для відображення ключів кодування.
Функція, яку ви надаєте, приймає a [CodingKey], який представляє шлях кодування для поточної точки кодування / декодування (у більшості випадків вам потрібно буде враховувати лише останній елемент; тобто поточний ключ). Функція повертає a, 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перерахунком; чи можу я просто перерахувати один ключ, який я змінюю?