Как я могу использовать пользовательские ключи с протоколом Свифт 4-х Декодируемые?
Swift 4 представила поддержку собственного кодирования и декодирования JSON через Decodable протокол. Как я могу использовать пользовательские ключи для этого?
например, скажем, у меня есть структура
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
Я могу закодировать это в 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"
}
}
}
Я могу закодировать это обратно в объект.
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
но если бы у меня был объект json, который был
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
как бы я сказал декодеру на Address это zip_code карты zip? Я считаю вы используете новый CodingKey протокол, но я не могу понять, как это использовать.
2 ответа:
ручная настройка ключей кодирования
в вашем примере, вы получаете автоматически сгенерированный соответствия
один действительно особенность этого авто-сгенерированный соответствия заключается в том, что если вы определяете вложенный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 } }имена вариантов перечисления должны соответствовать именам свойств и необработанным значениям этих случаи должны соответствовать ключам, которые вы кодируете/декодируете (если не указано иное, необработанные значения a
Stringперечисление будет таким же, как в случае имен). Таким образом,zipсвойство теперь будет закодировано / декодировано с помощью ключа"zip_code".точные правила для автоматически генерируемых
Encodable/Decodableсоответствие детализировано эволюция предложение (выделено мной):In дополнение к автоматическому
CodingKeyсинтез требованиеenums,Encodable&Decodableтребования могут быть автоматически синтезируется также для некоторых типов:
типы, соответствующие
Encodableсвойства которых всеEncodableполучить автоматически сгенерированныйString-backedCodingKeyсопоставление перечисление свойства для имен дел. Аналогично дляDecodableтипы, свойства всеDecodableтипы, попадающие в (1) - и типы, которые вручную предоставляют
CodingKeyenum(название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 Design guidelines, следует равномерно верхний или нижний регистр (в зависимости от позиции).
например, свойство с именем
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")
С Swift 4.2, В соответствии с вашими потребностями, вы можете использовать одну из 3 следующих стратегий для того, чтобы ваши имена пользовательских свойств объектов модели соответствовали вашим ключам JSON.
#1. Использование пользовательских ключей кодирования
когда вы объявляете структуру, которая соответствует
Codable(DecodableиEncodableпротоколы) со следующей реализацией...struct Address: Codable { var street: String var zip: String var city: String var state: String }... компилятор автоматически создает вложенное перечисление, которое соответствует
CodingKeyпротокол для вас.struct Address: Codable { var street: String var zip: String var city: String var state: String // compiler generated private enum CodingKeys: String, CodingKey { case street case zip case city case state } }поэтому, если ключи, используемые в вашем сериализованном формате данных, не соответствуют именам свойств из вашего типа данных, вы можете вручную реализовать это перечисление и установить соответствующий
rawValueдля необходимых случаев.в следующем примере показано, как это сделать:
import Foundation struct Address: Codable { var street: String var zip: String var city: String var state: String private enum CodingKeys: String, CodingKey { case street case zip = "zip_code" case city case state } }let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") let encoder = JSONEncoder() if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} */let jsonString = """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} """ let decoder = JSONDecoder() if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") */
#2. Использование змея случае верблюда случае ключевых стратегий кодирования
если у вашего JSON есть ключи в змеиной оболочке, и вы хотите чтобы преобразовать их в свойства camel-cased для вашего объекта модели, вы можете установить
JSONEncoder' skeyEncodingStrategyиJSONDecoder' skeyDecodingStrategyсвойства.convertToSnakeCase.в следующем примере показано, как это сделать:
import Foundation struct Address: Codable { var street: String var zipCode: String var cityName: String var state: String }let address = Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"} */let jsonString = """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"} """ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California") */
#3. Использование пользовательских стратегий кодирования ключей
при необходимости
JSONEncoderиJSONDecoderпозволяет установить пользовательскую стратегию для отображения ключей кодирования с помощьюJSONEncoder.KeyEncodingStrategy.custom(_:)иJSONDecoder.KeyDecodingStrategy.custom(_:).в следующем примере показано, как реализовать их:
import Foundation struct Address: Codable { var street: String var zip: String var city: String var state: String } struct AnyKey: CodingKey { var stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } }let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .custom({ (keys) -> CodingKey in let lastKey = keys.last! guard lastKey.intValue == nil else { return lastKey } let stringValue = lastKey.stringValue.prefix(1).uppercased() + lastKey.stringValue.dropFirst() return AnyKey(stringValue: stringValue)! }) if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) } /* prints: {"Zip":"94608","Street":"Apple Bay Street","City":"Emeryville","State":"California"} */let jsonString = """ {"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"} """ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in let lastKey = keys.last! guard lastKey.intValue == nil else { return lastKey } let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst() return AnyKey(stringValue: stringValue)! }) if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) { print(address) } /* prints: Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California") */
источники: