read - swift 4 json parsing
¿Cómo uso las teclas personalizadas con el protocolo Decodable de Swift 4? (4)
Personalización manual de claves de codificación
En su ejemplo, está obteniendo una conformidad
Codable
con
Codable
ya que todas sus propiedades también se ajustan a
Codable
.
Esta conformidad crea automáticamente un tipo de clave que simplemente corresponde a los nombres de propiedad, que luego se utiliza para codificar / decodificar desde un único contenedor con clave.
Sin embargo, una característica
realmente
clara de esta conformidad autogenerada es que si define una
enum
anidada en su tipo llamada "
CodingKeys
" (o usa un
typealias
con este nombre) que se ajusta al protocolo
CodingKey
, Swift lo usará automáticamente como la clave tipo.
Por lo tanto, esto le permite personalizar fácilmente las claves con las que sus propiedades están codificadas / decodificadas.
Entonces, lo que esto significa es que puedes decir:
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
}
}
Los nombres de caso de enumeración deben coincidir con los nombres de propiedad, y los valores sin formato de estos casos deben coincidir con las claves de las que está codificando / decodificando (a menos que se especifique lo contrario, los valores sin formato de una enumeración
String
serán los mismos que los del caso nombres).
Por lo tanto, la propiedad
zip
ahora se codificará / decodificará utilizando la clave
"zip_code"
.
La
propuesta de evolución
(énfasis mío) detalla las reglas exactas para la conformidad
Encodable
/
Encodable
generada automáticamente:
Además de la síntesis automática de requisitos de
enums
paraenums
, los requisitosEncodable
yEncodable
pueden sintetizarse automáticamente para ciertos tipos:
Los tipos que se ajustan a
Encodable
cuyas propiedades son todasEncodable
obtienen automáticamente propiedades de mapeo de enumeración deCodingKey
respaldadas porCodingKey
a nombres de casos. De manera similar para los tiposDecodable
cuyas propiedades son todasDecodable
Los tipos incluidos en (1) y los tipos que proporcionan manualmente una
enum
CodingKey
(denominadaCodingKeys
, directamente o mediante untypealias
) cuyos casos se asignan de 1 a 1 a propiedadesEncodable
/Encodable
por nombre , obtienen una síntesis automática deinit(from:)
yencode(to:)
según corresponda, utilizando esas propiedades y clavesLos tipos que caen dentro de (1) ni (2) tendrán que proporcionar un tipo de clave personalizada si es necesario y proporcionar su propio
init(from:)
yencode(to:)
, según corresponda
Codificación de ejemplo:
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"}
Ejemplo de decodificación:
// 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
automáticas
snake_case
JSON para nombres de propiedad
camelCase
En Swift 4.1, si cambia el nombre de su propiedad
zip
a
zipCode
, puede aprovechar las estrategias de codificación / decodificación de
JSONEncoder
en
JSONEncoder
y
JSONDecoder
para convertir automáticamente las claves de codificación entre
camelCase
y
snake_case
.
Codificación de ejemplo:
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"}
Ejemplo de decodificación:
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")
Sin embargo, una cosa importante a tener en cuenta sobre esta estrategia es que no podrá hacer un viaje de ida y vuelta con algunos nombres de propiedad con acrónimos o iniciales que, de acuerdo con las pautas de diseño de la API de Swift , deberían estar uniformemente en mayúsculas o minúsculas (dependiendo de la posición )
Por ejemplo, una propiedad llamada
someURL
se codificará con la clave
some_url
, pero en la decodificación, se transformará en
someUrl
.
Para solucionar esto, tendrá que especificar manualmente la clave de codificación para que esa propiedad sea una cadena que el decodificador espera, por ejemplo,
someUrl
en este caso (que el codificador seguirá transformando a
some_url
):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(Esto no responde estrictamente a su pregunta específica, pero dada la naturaleza canónica de estas preguntas y respuestas, creo que vale la pena incluirla)
Asignación de teclas JSON automática personalizada
En Swift 4.1, puede aprovechar las estrategias de codificación / decodificación de claves personalizadas en
JSONEncoder
y
JSONDecoder
, lo que le permite proporcionar una función personalizada para asignar claves de codificación.
La función que proporciona toma una
[CodingKey]
, que representa la ruta de codificación para el punto actual de codificación / decodificación (en la mayoría de los casos, solo tendrá que considerar el último elemento; es decir, la clave actual).
La función devuelve una
CodingKey
que reemplazará la última clave de esta matriz.
Por ejemplo, claves JSON de
lowerCamelCase
para nombres de propiedad
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
}
}
}
Ahora puede codificar con la estrategia clave
.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"}
y decodifique con la estrategia clave
.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 introdujo soporte para la codificación y decodificación JSON nativas a través del protocolo
Decodable
.
¿Cómo uso las teclas personalizadas para esto?
Por ejemplo, digamos que tengo una estructura
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
Puedo codificar esto a 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"
}
}
}
Puedo codificar esto de nuevo a un objeto.
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
Pero si tuviera un objeto json que fuera
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
¿Cómo le diría al decodificador en
Address
que
zip_code
asigna a
zip
?
Creo que usa el nuevo protocolo
CodingKey
, pero no puedo entender cómo usarlo.
Al usar CodingKey puede usar claves personalizadas en protocolos codificables o decodificables.
struct person: Codable {
var name: String
var age: Int
var street: String
var state: String
private enum CodingKeys: String, CodingKey {
case name
case age
case street = "Street_name"
case state
} }
Con Swift 4.2, de acuerdo con sus necesidades, puede usar una de las 3 estrategias siguientes para hacer que sus nombres de propiedades personalizadas de objetos de modelo coincidan con sus claves JSON.
# 1 Usar teclas de codificación personalizadas
Cuando declara una estructura que se ajusta a
Codable
(protocolos
Encodable
y
Encodable
) con la siguiente implementación ...
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
}
... el compilador genera automáticamente una enumeración anidada que se ajusta al protocolo
CodingKey
por usted.
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
}
}
Por lo tanto, si las claves utilizadas en su formato de datos serializados no coinciden con los nombres de propiedad de su tipo de datos, puede implementar manualmente esta enumeración y establecer el valor
rawValue
apropiado para los casos necesarios.
El siguiente ejemplo muestra cómo hacerlo:
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
}
}
Codificar (reemplazando la propiedad
zip
con la clave JSON "zip_code"):
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"}
*/
Decodificación (reemplazando la clave JSON "zip_code" con la propiedad
zip
):
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 Usando el caso de la serpiente a las estrategias de codificación clave de casos de camellos
Si su JSON tiene claves en forma de serpiente y desea convertirlas en propiedades en forma de camello para su objeto modelo, puede establecer las
JSONEncoder
de
keyEncodingStrategy
y
JSONDecoder
de
keyDecodingStrategy
en
.convertToSnakeCase
.
El siguiente ejemplo muestra cómo hacerlo:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
Codificar (convertir las propiedades en camello en claves JSON en serpiente):
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"}
*/
Decodificación (conversión de claves JSON con carcasa de serpiente en propiedades con carcasa de camello):
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 Usar estrategias de codificación de clave personalizadas
Si es necesario,
JSONEncoder
y
JSONDecoder
permiten establecer una estrategia personalizada para asignar claves de codificación utilizando
JSONEncoder.KeyEncodingStrategy.custom(_:)
y
JSONDecoder.KeyDecodingStrategy.custom(_:)
.
El siguiente ejemplo muestra cómo implementarlos:
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
}
}
Codificar (convertir las propiedades de la primera letra en minúscula en claves JSON de primera letra en mayúscula):
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"}
*/
Decodificación (conversión de claves JSON de primera letra en mayúsculas en propiedades de primera letra en minúscula):
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")
*/
Fuentes:
Lo que he hecho es crear una estructura propia al igual que lo que obtienes del JSON con respecto a sus tipos de datos.
Solo así:
struct Track {
let id : Int
let contributingArtistNames:String
let name : String
let albumName :String
let copyrightP:String
let copyrightC:String
let playlistCount:Int
let trackPopularity:Int
let playlistFollowerCount:Int
let artistFollowerCount : Int
let label : String
}
Después de esto, debe crear una extensión de la misma
struct
extienda
decodable
y la
enum
de la misma estructura con
CodingKey
y luego debe inicializar el decodificador utilizando esta enumeración con sus claves y tipos de datos (las claves vendrán de la enumeración y los tipos de datos venir o decir referenciado desde la estructura misma)
extension Track: Decodable {
enum TrackCodingKeys: String, CodingKey {
case id = "id"
case contributingArtistNames = "primaryArtistsNames"
case spotifyId = "spotifyId"
case name = "name"
case albumName = "albumName"
case albumImageUrl = "albumImageUrl"
case copyrightP = "copyrightP"
case copyrightC = "copyrightC"
case playlistCount = "playlistCount"
case trackPopularity = "trackPopularity"
case playlistFollowerCount = "playlistFollowerCount"
case artistFollowerCount = "artistFollowers"
case label = "label"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.id){
id = try trackContainer.decode(Int.self, forKey: .id)
}else{
id = 0
}
if trackContainer.contains(.contributingArtistNames){
contributingArtistNames = try trackContainer.decode(String.self, forKey: .contributingArtistNames)
}else{
contributingArtistNames = ""
}
if trackContainer.contains(.spotifyId){
spotifyId = try trackContainer.decode(String.self, forKey: .spotifyId)
}else{
spotifyId = ""
}
if trackContainer.contains(.name){
name = try trackContainer.decode(String.self, forKey: .name)
}else{
name = ""
}
if trackContainer.contains(.albumName){
albumName = try trackContainer.decode(String.self, forKey: .albumName)
}else{
albumName = ""
}
if trackContainer.contains(.albumImageUrl){
albumImageUrl = try trackContainer.decode(String.self, forKey: .albumImageUrl)
}else{
albumImageUrl = ""
}
if trackContainer.contains(.copyrightP){
copyrightP = try trackContainer.decode(String.self, forKey: .copyrightP)
}else{
copyrightP = ""
}
if trackContainer.contains(.copyrightC){
copyrightC = try trackContainer.decode(String.self, forKey: .copyrightC)
}else{
copyrightC = ""
}
if trackContainer.contains(.playlistCount){
playlistCount = try trackContainer.decode(Int.self, forKey: .playlistCount)
}else{
playlistCount = 0
}
if trackContainer.contains(.trackPopularity){
trackPopularity = try trackContainer.decode(Int.self, forKey: .trackPopularity)
}else{
trackPopularity = 0
}
if trackContainer.contains(.playlistFollowerCount){
playlistFollowerCount = try trackContainer.decode(Int.self, forKey: .playlistFollowerCount)
}else{
playlistFollowerCount = 0
}
if trackContainer.contains(.artistFollowerCount){
artistFollowerCount = try trackContainer.decode(Int.self, forKey: .artistFollowerCount)
}else{
artistFollowerCount = 0
}
if trackContainer.contains(.label){
label = try trackContainer.decode(String.self, forKey: .label)
}else{
label = ""
}
}
}
Debe cambiar aquí todas y cada una de las claves y tipos de datos según sus necesidades y utilizarlo con el decodificador.