json - puedo - que libros es recomendable leer para ser mas inteligente
Swift 4 JSON La forma más fácil de decodificar para decodificar el cambio de tipo (6)
Con el protocolo Codable de swift4, hay un gran nivel de estrategias de conversión de datos y datos bajo el capó.
Dado el JSON:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
Quiero coaccionarlo en la siguiente estructura.
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
La estrategia de decodificación de fecha puede convertir una fecha basada en cadena en una fecha.
¿Hay algo que hace eso con un flotador basado en cadenas?
De lo contrario, me quedé atascado con el uso de CodingKey para ingresar una cadena y usar un objeto informático:
enum CodingKeys: String, CodingKey {
case name, age
case sTaxRate = "tax_rate"
}
var sTaxRate: String
var taxRate: Float { return Float(sTaxRate) ?? 0.0 }
Este tipo de hilos me hacen más mantenimiento del que parece que debería ser necesario.
¿Es esta la forma más sencilla o hay algo similar a DateDecodingStrategy para otras conversiones de tipos?
Actualización : Debo tener en cuenta: También he ido por la ruta de anulación
init(from decoder:Decoder)
Pero eso es en la dirección opuesta, ya que me obliga a hacerlo todo por mí mismo.
De acuerdo con sus necesidades, puede elegir una de las dos formas siguientes para resolver su problema.
# 1. Usando Decodable
init(from:)
initializer
Use esta estrategia cuando necesite convertir de String
a Float
para una sola estructura, enumeración o clase.
import Foundation
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: CodingKeys.name)
age = try container.decode(Int.self, forKey: CodingKeys.age)
let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate)
guard let taxRateFloat = Float(taxRateString) else {
let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = taxRateFloat
}
}
Uso:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
# 2. Usando un modelo intermedio
Use esta estrategia cuando tenga muchas claves anidadas en su JSON o cuando necesite convertir muchas claves (por ejemplo, de String
a Float
) de su JSON.
import Foundation
fileprivate struct PrivateExampleJson: Decodable {
var name: String
var age: Int
var taxRate: String
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
init(from decoder: Decoder) throws {
let privateExampleJson = try PrivateExampleJson(from: decoder)
name = privateExampleJson.name
age = privateExampleJson.age
guard let convertedTaxRate = Float(privateExampleJson.taxRate) else {
let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = convertedTaxRate
}
}
Uso:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
Desafortunadamente, no creo que exista tal opción en la API de JSONDecoder
actual. Solo existe una opción para convertir valores de punto flotante excepcionales hacia y desde una representación de cadena.
Otra posible solución para decodificar manualmente es definir un tipo de envoltorio Codable
para cualquier LosslessStringConvertible
que pueda codificar y decodificar desde su representación de String
:
struct StringCodableMap<Decoded : LosslessStringConvertible> : Codable {
var decoded: Decoded
init(_ decoded: Decoded) {
self.decoded = decoded
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)
guard let decoded = Decoded(decodedString) else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: """
The string /(decodedString) is not representable as a /(Decoded.self)
"""
)
}
self.decoded = decoded
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(decoded.description)
}
}
Entonces puede tener una propiedad de este tipo y usar la conformidad Codable
generada Codable
:
struct Example : Codable {
var name: String
var age: Int
var taxRate: StringCodableMap<Float>
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
Aunque desafortunadamente, ahora tiene que hablar en términos de taxRate.decoded
para interactuar con el valor Float
.
Sin embargo, siempre se podría definir una propiedad computada de reenvío simple para aliviar esto:
struct Example : Codable {
var name: String
var age: Int
private var _taxRate: StringCodableMap<Float>
var taxRate: Float {
get { return _taxRate.decoded }
set { _taxRate.decoded = newValue }
}
private enum CodingKeys: String, CodingKey {
case name, age
case _taxRate = "tax_rate"
}
}
Aunque esto todavía no es tan simple como debería ser, con suerte, una versión posterior de la API JSONDecoder
incluirá más opciones de decodificación personalizadas, o bien tendrá la capacidad de expresar conversiones de tipos dentro de la API Codable
.
Sin embargo, una de las ventajas de crear el tipo de envoltorio es que también se puede utilizar para simplificar la descodificación y la codificación manual. Por ejemplo, con decodificación manual:
struct Example : Decodable {
var name: String
var age: Int
var taxRate: Float
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.taxRate = try container.decode(StringCodableMap<Float>.self,
forKey: .taxRate).decoded
}
}
Puedes usar la lazy var
para convertir la propiedad a otro tipo:
struct ExampleJson: Decodable {
var name: String
var age: Int
lazy var taxRate: Float = {
Float(self.tax_rate)!
}()
private var tax_rate: String
}
Una desventaja de este enfoque es que no puede definir una constante de let
si desea acceder a taxRate
, ya que la primera vez que accede a ella, está mutando la estructura.
// Cannot use `let` here
var example = try! JSONDecoder().decode(ExampleJson.self, from: data)
Sé que esta es una respuesta muy tardía, pero comencé a trabajar en Codable
hace solo unos días. Y me topé con un problema similar.
Para convertir la cadena en un número flotante, puede escribir una extensión a KeyedDecodingContainer
y llamar al método en la extensión desde init(from decoder: Decoder){}
Para el problema mencionado en este problema, vea la extensión que escribí a continuación;
extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
return nil
}
return Float(value)
}
func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
return Float(try decode(transformFrom, forKey: key))
}
}
Puede llamar a este método desde el método init(from decoder: Decoder)
. Vea un ejemplo a continuación;
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self)
}
De hecho, puede utilizar este enfoque para convertir cualquier tipo de datos a cualquier otro tipo. Puede convertir la string to Date
, string to bool
, string to float
, float to int
etc.
En realidad, para convertir una cadena en un objeto Fecha, preferiré este enfoque sobre JSONEncoder().dateEncodingStrategy
porque si lo escribe correctamente, puede incluir diferentes formatos de fecha en la misma respuesta.
Espero haberte ayudado.
Siempre se puede decodificar manualmente. Entonces, dado:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
Tu puedes hacer:
struct Example: Codable {
let name: String
let age: Int
let taxRate: Float
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
guard let rate = try Float(values.decode(String.self, forKey: .taxRate)) else {
throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.taxRate], debugDescription: "Expecting string representation of Float"))
}
taxRate = rate
}
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
Consulte Codificar y decodificar manualmente en Codificación y decodificación de tipos personalizados .
Pero estoy de acuerdo, que parece que debería haber un proceso de conversión de cadena más elegante equivalente a DateDecodingStrategy
dada la cantidad de fuentes JSON que hay que devolver incorrectamente los valores numéricos como cadenas.
introduce la descripción del enlace aquí Cómo utilizar JSONDecodable en Swift4
1) obtener la Respuesta JSON y Crear Struct 2) conformar la clase Decodable en Struct 3) Otros pasos en el siguiente Proyecto (Ejemplo simple)