Personalização manual das chaves de codificação
Em seu exemplo, você está obtendo uma conformidade gerada automaticamente para, Codablejá 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 enumem seu tipo chamado " CodingKeys" (ou usar um typealiascom 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 zippropriedade agora será codificada / decodificada usando a chave "zip_code".
As regras exatas para a autogeração Encodable/ Decodableconformidade são detalhadas por na proposta de evolução (grifo meu):
Além da CodingKeysíntese automática de requisitos para
enums, Encodablee os Decodablerequisitos também podem ser sintetizados automaticamente para certos tipos:
Os tipos em conformidade com as Encodablepropriedades de cada Encodableum Stringobtêm CodingKeypropriedades de mapeamento enum geradas automaticamente para nomes de caso. Da mesma forma para Decodabletipos 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/ Decodablepropriedades 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_caseChaves JSON automáticas para camelCasenomes de propriedades
No Swift 4.1, se você renomear sua zippropriedade para zipCode, poderá aproveitar as vantagens das estratégias de codificação / decodificação de chave em JSONEncodere JSONDecoderpara converter automaticamente as chaves de codificação entre camelCasee 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 someURLserá 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_urlpelo 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 JSONEncodere 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 CodingKeyque substituirá a última chave nesta matriz.
Por exemplo, UpperCamelCasechaves JSON para lowerCamelCasenomes 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 .convertToUpperCamelCaseestraté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 .convertFromUpperCamelCaseestraté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")
CodingKeysenum; posso apenas listar a chave que estou mudando?