type read protocol parse not jsonserialization does data json swift swift4 codable

read - string to json swift 4



Cómo decodificar una propiedad con un tipo de diccionario JSON en el protocolo decodificable Swift 4 (11)

Digamos que tengo el tipo de datos del Customer que contiene una propiedad de metadata que puede contener cualquier diccionario JSON en el objeto del cliente

struct Customer { let id: String let email: String let metadata: [String: Any] }

{ "object": "customer", "id": "4yq6txdpfadhbaqnwp3", "email": "[email protected]", "metadata": { "link_id": "linked-id", "buy_count": 4 } }

La propiedad de metadata puede ser cualquier objeto de mapa JSON arbitrario.

Antes de poder emitir la propiedad desde un JSON deserializado de NSJSONDeserialization pero con el nuevo protocolo Swift 4 Decodable , todavía no puedo pensar en una forma de hacerlo.

¿Alguien sabe cómo lograr esto en Swift 4 con el protocolo Decodable?


Aquí hay más genérico (no solo [String: Any] , sino que [Any] puede decodificarse) y un enfoque encapsulado (se utiliza una entidad separada para eso) inspirado en la respuesta @loudmouth.

Usarlo se verá así:

extension Customer: Decodable { public init(from decoder: Decoder) throws { let selfContainer = try decoder.container(keyedBy: CodingKeys.self) id = try selfContainer.decode(.id) email = try selfContainer.decode(.email) let metadataContainer: JsonContainer = try selfContainer.decode(.metadata) guard let metadata = metadataContainer.value as? [String: Any] else { let context = DecodingError.Context(codingPath: [CodingKeys.metadata], debugDescription: "Expected ''[String: Any]'' for ''metadata'' key") throw DecodingError.typeMismatch([String: Any].self, context) } self.metadata = metadata } private enum CodingKeys: String, CodingKey { case id, email, metadata } }

JsonContainer es una entidad auxiliar que utilizamos para envolver datos JSON de decodificación en objetos JSON (matriz o diccionario) sin extender *DecodingContainer (por lo que no interferirá con casos raros cuando un objeto JSON no se entiende por [String: Any] ).

struct JsonContainer { let value: Any } extension JsonContainer: Decodable { public init(from decoder: Decoder) throws { if let keyedContainer = try? decoder.container(keyedBy: Key.self) { var dictionary = [String: Any]() for key in keyedContainer.allKeys { if let value = try? keyedContainer.decode(Bool.self, forKey: key) { // Wrapping numeric and boolean types in `NSNumber` is important, so `as? Int64` or `as? Float` casts will work dictionary[key.stringValue] = NSNumber(value: value) } else if let value = try? keyedContainer.decode(Int64.self, forKey: key) { dictionary[key.stringValue] = NSNumber(value: value) } else if let value = try? keyedContainer.decode(Double.self, forKey: key) { dictionary[key.stringValue] = NSNumber(value: value) } else if let value = try? keyedContainer.decode(String.self, forKey: key) { dictionary[key.stringValue] = value } else if (try? keyedContainer.decodeNil(forKey: key)) ?? false { // NOP } else if let value = try? keyedContainer.decode(JsonContainer.self, forKey: key) { dictionary[key.stringValue] = value.value } else { throw DecodingError.dataCorruptedError(forKey: key, in: keyedContainer, debugDescription: "Unexpected value for /(key.stringValue) key") } } value = dictionary } else if var unkeyedContainer = try? decoder.unkeyedContainer() { var array = [Any]() while !unkeyedContainer.isAtEnd { let container = try unkeyedContainer.decode(JsonContainer.self) array.append(container.value) } value = array } else if let singleValueContainer = try? decoder.singleValueContainer() { if let value = try? singleValueContainer.decode(Bool.self) { self.value = NSNumber(value: value) } else if let value = try? singleValueContainer.decode(Int64.self) { self.value = NSNumber(value: value) } else if let value = try? singleValueContainer.decode(Double.self) { self.value = NSNumber(value: value) } else if let value = try? singleValueContainer.decode(String.self) { self.value = value } else if singleValueContainer.decodeNil() { value = NSNull() } else { throw DecodingError.dataCorruptedError(in: singleValueContainer, debugDescription: "Unexpected value") } } else { let context = DecodingError.Context(codingPath: [], debugDescription: "Invalid data format for JSON") throw DecodingError.dataCorrupted(context) } } private struct Key: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { self.init(stringValue: "/(intValue)") self.intValue = intValue } } }

Tenga en cuenta que los tipos numéricos y booleanos están respaldados por NSNumber , de lo contrario, algo como esto no funcionará:

if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil


Con algo de inspiración de esta esencia que encontré, escribí algunas extensiones para UnkeyedDecodingContainer y KeyedDecodingContainer . Puede encontrar un enlace a mi esencia here . Al usar este código, ahora puede decodificar cualquier Array<Any> o Dictionary<String, Any> con la sintaxis familiar:

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)

o

let array: [Any] = try container.decode([Any].self, forKey: key)

Editar: hay una advertencia que he encontrado que está decodificando una matriz de diccionarios [[String: Any]] La sintaxis requerida es la siguiente. Es probable que desee arrojar un error en lugar de forzar el lanzamiento:

let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]

EDIT 2: si simplemente desea convertir un archivo completo en un diccionario, es mejor quedarse con la API de JSONSerialization ya que no he encontrado una manera de extender JSONDecoder para decodificar directamente un diccionario.

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { // appropriate error handling return }

Las extensiones

// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a struct JSONCodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { self.init(stringValue: "/(intValue)") self.intValue = intValue } } extension KeyedDecodingContainer { func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> { let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) return try container.decode(type) } func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? { guard contains(key) else { return nil } guard try decodeNil(forKey: key) == false else { return nil } return try decode(type, forKey: key) } func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> { var container = try self.nestedUnkeyedContainer(forKey: key) return try container.decode(type) } func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? { guard contains(key) else { return nil } guard try decodeNil(forKey: key) == false else { return nil } return try decode(type, forKey: key) } func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { var dictionary = Dictionary<String, Any>() for key in allKeys { if let boolValue = try? decode(Bool.self, forKey: key) { dictionary[key.stringValue] = boolValue } else if let stringValue = try? decode(String.self, forKey: key) { dictionary[key.stringValue] = stringValue } else if let intValue = try? decode(Int.self, forKey: key) { dictionary[key.stringValue] = intValue } else if let doubleValue = try? decode(Double.self, forKey: key) { dictionary[key.stringValue] = doubleValue } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) { dictionary[key.stringValue] = nestedDictionary } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) { dictionary[key.stringValue] = nestedArray } } return dictionary } } extension UnkeyedDecodingContainer { mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> { var array: [Any] = [] while isAtEnd == false { // See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays. if try decodeNil() { continue } else if let value = try? decode(Bool.self) { array.append(value) } else if let value = try? decode(Double.self) { array.append(value) } else if let value = try? decode(String.self) { array.append(value) } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) { array.append(nestedDictionary) } else if let nestedArray = try? decode(Array<Any>.self) { array.append(nestedArray) } } return array } mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) return try nestedContainer.decode(type) } }


Cuando encontré la respuesta anterior, solo probé un caso de objeto JSON simple pero no vacío, lo que causará una excepción de tiempo de ejecución como @slurmomatic y @zoul encontrado. Perdón por este problema.

Así que intento de otra manera al tener un protocolo JSONValue simple, implementar la estructura de borrado de tipo AnyJSONValue y usar ese tipo en lugar de Any . Aquí hay una implementación.

public protocol JSONType: Decodable { var jsonValue: Any { get } } extension Int: JSONType { public var jsonValue: Any { return self } } extension String: JSONType { public var jsonValue: Any { return self } } extension Double: JSONType { public var jsonValue: Any { return self } } extension Bool: JSONType { public var jsonValue: Any { return self } } public struct AnyJSONType: JSONType { public let jsonValue: Any public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let intValue = try? container.decode(Int.self) { jsonValue = intValue } else if let stringValue = try? container.decode(String.self) { jsonValue = stringValue } else if let boolValue = try? container.decode(Bool.self) { jsonValue = boolValue } else if let doubleValue = try? container.decode(Double.self) { jsonValue = doubleValue } else if let doubleValue = try? container.decode(Array<AnyJSONType>.self) { jsonValue = doubleValue } else if let doubleValue = try? container.decode(Dictionary<String, AnyJSONType>.self) { jsonValue = doubleValue } else { throw DecodingError.typeMismatch(JSONType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep")) } } }

Y aquí está cómo usarlo al decodificar

metadata = try container.decode ([String: AnyJSONValue].self, forKey: .metadata)

El problema con este problema es que debemos llamar a value.jsonValue as? Int value.jsonValue as? Int . Necesitamos esperar hasta que Conditional Conformance aterrice en Swift, eso resolvería este problema o al menos lo ayudaría a mejorar.

[Respuesta anterior]

Publico esta pregunta en el foro de desarrolladores de Apple y resulta que es muy fácil.

puedo hacer

metadata = try container.decode ([String: Any].self, forKey: .metadata)

en el inicializador

En primer lugar, fue mi mal echar de menos eso.


He creado un pod para facilitar la forma de decodificar + codificar [String: Any] , [Any] . Y esto proporciona codificar o decodificar las propiedades opcionales, aquí https://github.com/levantAJ/AnyCodable

pod ''DynamicCodable'', ''1.0''

Cómo usarlo:

import DynamicCodable struct YourObject: Codable { var dict: [String: Any] var array: [Any] var optionalDict: [String: Any]? var optionalArray: [Any]? enum CodingKeys: String, CodingKey { case dict case array case optionalDict case optionalArray } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) dict = try values.decode([String: Any].self, forKey: .dict) array = try values.decode([Any].self, forKey: .array) optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict) optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(dict, forKey: .dict) try container.encode(array, forKey: .array) try container.encodeIfPresent(optionalDict, forKey: .optionalDict) try container.encodeIfPresent(optionalArray, forKey: .optionalArray) } }


La forma más fácil y sugerida es crear un modelo separado para cada diccionario o modelo que esté en JSON .

Esto es lo que hago

//Model for dictionary **Metadata** struct Metadata: Codable { var link_id: String? var buy_count: Int? } //Model for dictionary **Customer** struct Customer: Codable { var object: String? var id: String? var email: String? var metadata: Metadata? } //Here is our decodable parser that decodes JSON into expected model struct CustomerParser { var customer: Customer? } extension CustomerParser: Decodable { //keys that matches exactly with JSON enum CustomerKeys: String, CodingKey { case object = "object" case id = "id" case email = "email" case metadata = "metadata" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CustomerKeys.self) // defining our (keyed) container let object: String = try container.decode(String.self, forKey: .object) // extracting the data let id: String = try container.decode(String.self, forKey: .id) // extracting the data let email: String = try container.decode(String.self, forKey: .email) // extracting the data //Here I have used metadata model instead of dictionary [String: Any] let metadata: Metadata = try container.decode(Metadata.self, forKey: .metadata) // extracting the data self.init(customer: Customer(object: object, id: id, email: email, metadata: metadata)) } }

Uso:

if let url = Bundle.main.url(forResource: "customer-json-file", withExtension: "json") { do { let jsonData: Data = try Data(contentsOf: url) let parser: CustomerParser = try JSONDecoder().decode(CustomerParser.self, from: jsonData) print(parser.customer ?? "null") } catch { } }

** He utilizado opcional para estar seguro mientras se analiza, se puede cambiar según sea necesario.

Leer más sobre este tema


Lo que quieres ir en contra del diseño de Codable . La idea detrás de Codable es proporcionar un mecanismo para archivar y desarchivar datos de forma segura. Esto significa que debe definir las propiedades y sus tipos de datos de antemano. Puedo pensar en 2 soluciones a su problema:

1. Enumere todas las claves de metadatos potenciales

A menudo, si llega lo suficientemente profundo a la documentación de la API, encontrará la lista completa de todas las claves de metadatos potenciales. Defina una estructura de Metadata , con estas claves como propiedades opcionales:

struct Customer: Decodable { struct Metadata: Decodable { var linkId: String? var buyCount: Int? var somethingElse: Int? private enum CodingKeys: String, CodingKey { case linkId = "link_id" case buyCount = "buy_count" case somethingElse = "something_else" } } var object: String var id: String var email: String var metadata: Metadata } let customer = try! JSONDecoder().decode(Customer.self, from: jsonData) print(customer.metadata)

Puedo ver que los diseñadores de Swift hubieran preferido este enfoque.

2. Combinar decodificación y serialización JSONS

JSONSerialization ofrece un gran dinamismo en la compensación por la seguridad del tipo Definitivamente puedes mezclarlo con Decodable , cuya filosofía de diseño es todo lo contrario:

struct Customer { private struct RawCustomer: Decodable { var object: String var id: String var email: String } var object: String var id: String var email: String var metadata: [String: AnyObject] init(jsonData: Data) throws { let rawCustomer = try JSONDecoder().decode(RawCustomer.self, from: jsonData) object = rawCustomer.object id = rawCustomer.id email = rawCustomer.email let jsonObject = try JSONSerialization.jsonObject(with: jsonData) if let dict = jsonObject as? [String: AnyObject], let metadata = dict["metadata"] as? [String: AnyObject] { self.metadata = metadata } else { self.metadata = [String: AnyObject]() } } } let customer = try! Customer(jsonData: jsonData) print(customer.metadata)


Puede crear una estructura de metadatos que confirme el protocolo Codable y usar la clase Decodable para crear un objeto como el siguiente.

let json: [String: Any] = [ "object": "customer", "id": "4yq6txdpfadhbaqnwp3", "email": "[email protected]", "metadata": [ "link_id": "linked-id", "buy_count": 4 ] ] struct Customer: Codable { let object: String let id: String let email: String let metadata: Metadata } struct Metadata: Codable { let link_id: String let buy_count: Int } let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) let decoder = JSONDecoder() do { let customer = try decoder.decode(Customer.self, from: data) print(customer) } catch { print(error.localizedDescription) }


Puede echar un vistazo a BeyovaJSON

import BeyovaJSON struct Customer: Codable { let id: String let email: String let metadata: JToken } //create a customer instance customer.metadata = ["link_id": "linked-id","buy_count": 4] let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted print(String(bytes: try! encoder.encode(customer), encoding: .utf8)!)


Si usa SwiftyJSON para analizar JSON, puede actualizar a 4.1.0 que tiene Codable protocolo Codable . Simplemente declare metadata: JSON y ya está todo listo.

import SwiftyJSON struct Customer { let id: String let email: String let metadata: JSON }


También jugué con este problema y finalmente escribí una biblioteca simple para trabajar con tipos "genéricos JSON" . (Donde "genérico" significa "sin estructura conocida de antemano".) El punto principal representa el JSON genérico con un tipo concreto:

public enum JSON { case string(String) case number(Float) case object([String:JSON]) case array([JSON]) case bool(Bool) case null }

Este tipo puede implementar Codable y Equatable .


Vine con una solución ligeramente diferente.

Supongamos que tenemos algo más que un simple [String: Any] para analizar donde Any podría ser una matriz o un diccionario anidado o un diccionario de matrices.

Algo como esto:

var json = """ { "id": 12345, "name": "Giuseppe", "last_name": "Lanza", "age": 31, "happy": true, "rate": 1.5, "classes": ["maths", "phisics"], "dogs": [ { "name": "Gala", "age": 1 }, { "name": "Aria", "age": 3 } ] } """

Bueno, esta es mi solución:

public struct AnyDecodable: Decodable { public var value: Any private struct CodingKeys: CodingKey { var stringValue: String var intValue: Int? init?(intValue: Int) { self.stringValue = "/(intValue)" self.intValue = intValue } init?(stringValue: String) { self.stringValue = stringValue } } public init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { var result = [String: Any]() try container.allKeys.forEach { (key) throws in result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value } value = result } else if var container = try? decoder.unkeyedContainer() { var result = [Any]() while !container.isAtEnd { result.append(try container.decode(AnyDecodable.self).value) } value = result } else if let container = try? decoder.singleValueContainer() { if let intVal = try? container.decode(Int.self) { value = intVal } else if let doubleVal = try? container.decode(Double.self) { value = doubleVal } else if let boolVal = try? container.decode(Bool.self) { value = boolVal } else if let stringVal = try? container.decode(String.self) { value = stringVal } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable") } } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise")) } } }

Pruébalo usando

let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any] print(stud)