Personalização manual das chaves de codificação
Em seu exemplo, você está obtendo uma conformidade gerada automaticamente para, Codable
já que todas as suas propriedades também estão em conformidade Codable
. Essa conformidade cria automaticamente um tipo de chave que simplesmente corresponde aos nomes das propriedades - que é então usado para codificar / decodificar a partir de um único contêiner com chave.
No entanto, uma característica realmente interessante desta conformidade gerada automaticamente é que se você definir um aninhado enum
em seu tipo chamado " CodingKeys
" (ou usar um typealias
com este nome) que está em conformidade com oCodingKey
protocolo - o Swift usará isso automaticamente como o tipo de chave. Isso, portanto, permite que você personalize facilmente as chaves com as quais suas propriedades são codificadas / decodificadas.
Então, isso significa que você pode apenas dizer:
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
}
}
Os nomes dos casos enum precisam corresponder aos nomes das propriedades, e os valores brutos desses casos precisam corresponder às chaves para as quais você está codificando / decodificando (a menos que especificado de outra forma, os valores brutos de um String
enumeração serão iguais aos nomes de caso ) Portanto, a zip
propriedade agora será codificada / decodificada usando a chave "zip_code"
.
As regras exatas para a autogeração Encodable
/ Decodable
conformidade são detalhadas por na proposta de evolução (grifo meu):
Além da CodingKey
síntese automática de requisitos para
enums
, Encodable
e os Decodable
requisitos também podem ser sintetizados automaticamente para certos tipos:
Os tipos em conformidade com as Encodable
propriedades de cada Encodable
um String
obtêm CodingKey
propriedades de mapeamento enum geradas automaticamente para nomes de caso. Da mesma forma para Decodable
tipos cujas propriedades são todasDecodable
Tipos que se enquadram em (1) - e tipos que fornecem manualmente um CodingKey
enum
(nomeado CodingKeys
, diretamente ou via a typealias
) cujos casos mapeiam 1-para-1 para Encodable
/ Decodable
propriedades por nome - obtêm síntese automática deinit(from:)
e encode(to:)
conforme apropriado, usando essas propriedades e chaves
Tipos que não se enquadram em (1) nem (2) terão que fornecer um tipo de chave personalizado se necessário e fornecer o seu próprio init(from:)
e
encode(to:)
, conforme apropriado
Codificação de exemplo:
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"}
Exemplo de decodificação:
// 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
Chaves JSON automáticas para camelCase
nomes de propriedades
No Swift 4.1, se você renomear sua zip
propriedade para zipCode
, poderá aproveitar as vantagens das estratégias de codificação / decodificação de chave em JSONEncoder
e JSONDecoder
para converter automaticamente as chaves de codificação entre camelCase
e snake_case
.
Codificação de exemplo:
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"}
Exemplo de decodificação:
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")
Uma coisa importante a notar sobre esta estratégia, no entanto, é que ela não será capaz de percorrer alguns nomes de propriedade com acrônimos ou iniciais que, de acordo com as diretrizes de design da API Swift , devem ser uniformemente maiúsculas ou minúsculas (dependendo da posição )
Por exemplo, uma propriedade nomeada someURL
será codificada com a chave some_url
, mas na decodificação, será transformada em someUrl
.
Para corrigir isso, você terá que especificar manualmente a chave de codificação dessa propriedade para ser a string que o decodificador espera, por exemplo someUrl
, neste caso (que ainda será transformada some_url
pelo codificador):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(Isso não responde estritamente à sua pergunta específica, mas dada a natureza canônica desta sessão de perguntas e respostas, acho que vale a pena incluí-la)
Mapeamento de teclas JSON automático personalizado
No Swift 4.1, você pode tirar proveito das estratégias de codificação / decodificação de chave personalizada em JSONEncoder
e JSONDecoder
, permitindo que você forneça uma função personalizada para mapear chaves de codificação.
A função que você fornece leva um [CodingKey]
, que representa o caminho de codificação para o ponto atual na codificação / decodificação (na maioria dos casos, você só precisa considerar o último elemento; ou seja, a chave atual). A função retorna um CodingKey
que substituirá a última chave nesta matriz.
Por exemplo, UpperCamelCase
chaves JSON para lowerCamelCase
nomes de propriedades:
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
}
}
}
Agora você pode codificar com a .convertToUpperCamelCase
estratégia principal:
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"}
e decodifique com a .convertFromUpperCamelCase
estratégia principal:
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; posso apenas listar a chave que estou mudando?