Как я могу использовать пользовательские ключи с протоколом Свифт 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) - и типы, которые вручную предоставляют
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 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") */
источники: