read json swift swift4 codable

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 para enums , los requisitos Encodable y Encodable pueden sintetizarse automáticamente para ciertos tipos:

  1. Los tipos que se ajustan a Encodable cuyas propiedades son todas Encodable obtienen automáticamente propiedades de mapeo de enumeración de CodingKey respaldadas por CodingKey a nombres de casos. De manera similar para los tipos Decodable cuyas propiedades son todas Decodable

  2. Los tipos incluidos en (1) y los tipos que proporcionan manualmente una enum CodingKey (denominada CodingKeys , directamente o mediante un typealias ) cuyos casos se asignan de 1 a 1 a propiedades Encodable / Encodable por nombre , obtienen una síntesis automática de init(from:) y encode(to:) según corresponda, utilizando esas propiedades y claves

  3. Los 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:) y encode(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.