protocol codingkeys swift swift4 codable

codingkeys - ¿Cómo puedo usar Swift''s Codable para codificar en un diccionario?



swift codable codingkeys (14)

Ahora que lo pienso, la pregunta no tiene una respuesta en el caso general, ya que la instancia Encodable puede ser algo no serializable en un diccionario, como una matriz:

let payload = [1, 2, 3] let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"

Aparte de eso, he escrito algo similar como un marco .

Tengo una estructura que implementa Swift 4''s Codable . ¿Hay una manera simple incorporada de codificar esa estructura en un diccionario?

let struct = Foo(a: 1, b: 2) let dict = something(struct) // now dict is ["a": 1, "b": 2]


Aquí hay implementaciones simples de DictionaryEncoder / DictionaryDecoder que envuelven JSONEncoder , JSONDecoder y JSONSerialization , que también manejan estrategias de codificación / decodificación ...

class DictionaryEncoder { private let encoder = JSONEncoder() var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy { set { encoder.dateEncodingStrategy = newValue } get { return encoder.dateEncodingStrategy } } var dataEncodingStrategy: JSONEncoder.DataEncodingStrategy { set { encoder.dataEncodingStrategy = newValue } get { return encoder.dataEncodingStrategy } } var nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy { set { encoder.nonConformingFloatEncodingStrategy = newValue } get { return encoder.nonConformingFloatEncodingStrategy } } var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy { set { encoder.keyEncodingStrategy = newValue } get { return encoder.keyEncodingStrategy } } func encode<T>(_ value: T) throws -> [String: Any] where T : Encodable { let data = try encoder.encode(value) return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any] } } class DictionaryDecoder { private let decoder = JSONDecoder() var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { set { decoder.dateDecodingStrategy = newValue } get { return decoder.dateDecodingStrategy } } var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy { set { decoder.dataDecodingStrategy = newValue } get { return decoder.dataDecodingStrategy } } var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy { set { decoder.nonConformingFloatDecodingStrategy = newValue } get { return decoder.nonConformingFloatDecodingStrategy } } var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy { set { decoder.keyDecodingStrategy = newValue } get { return decoder.keyDecodingStrategy } } func decode<T>(_ type: T.Type, from dictionary: [String: Any]) throws -> T where T : Decodable { let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) return try decoder.decode(type, from: data) } }

El uso es similar a JSONEncoder / JSONDecoder ...

let dictionary = try DictionaryEncoder().encode(object)

y

let object = try DictionaryDecoder().decode(Object.self, from: dictionary)

Por conveniencia, he puesto todo esto en un repositorio ... https://github.com/ashleymills/SwiftDictionaryCoding


Definitivamente creo que hay algo de valor en solo poder usar Codable para codificar a / desde diccionarios, sin la intención de golpear JSON / Plists / lo que sea. Hay muchas API que simplemente le devuelven un diccionario, o esperan un diccionario, y es bueno poder intercambiarlas fácilmente con estructuras u objetos Swift, sin tener que escribir un código repetitivo sin fin.

He estado jugando con algún código basado en la fuente Foundation JSONEncoder.swift (que en realidad implementa internamente la codificación / decodificación del diccionario, pero no lo exporta).

El código se puede encontrar aquí: https://github.com/elegantchaos/DictionaryCoding

Todavía es bastante difícil, pero lo he expandido un poco para que, por ejemplo, pueda completar los valores faltantes con los valores predeterminados al decodificar.


En algún proyecto, uso la rápida reflexión. Pero tenga cuidado, los objetos codificables anidados no se asignan también allí.

do { let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee) } catch let error { // handle error }


Escribí una gist rápida para manejar esto (sin usar el protocolo Codificable). Tenga cuidado, no verifica los valores y no funciona de forma recursiva en valores codificables.

class DictionaryEncoder { var result: [String: Any] init() { result = [:] } func encode(_ encodable: DictionaryEncodable) -> [String: Any] { encodable.encode(self) return result } func encode<T, K>(_ value: T, key: K) where K: RawRepresentable, K.RawValue == String { result[key.rawValue] = value } } protocol DictionaryEncodable { func encode(_ encoder: DictionaryEncoder) }


He creado una biblioteca llamada CodableFirebase y su propósito inicial era usarla con Firebase Database, pero en realidad hace lo que necesita: crea un diccionario o cualquier otro tipo al igual que en JSONDecoder pero no necesita hacer la doble conversión aquí como lo haces en otras respuestas. Entonces se vería algo así como:

import CodableFirebase let model = Foo(a: 1, b: 2) let dict = try! FirebaseEncoder().encode(model)


Hice un pod aquí https://github.com/levantAJ/AnyCodable para facilitar la decodificación y codificación [String: Any] y [Any]

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

Y puede decodificar y codificar [String: Any] y [Any]

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) } }


Modifiqué el PropertyListEncoder del proyecto Swift en un DictionaryEncoder, simplemente eliminando la serialización final del diccionario en formato binario. Puedes hacer lo mismo tú mismo, o puedes tomar mi código desde here

Se puede usar así:

let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })


No estoy seguro de si es la mejor manera, pero definitivamente puedes hacer algo como:

struct Foo: Codable { var a: Int var b: Int init(a: Int, b: Int) { self.a = a self.b = b } } let foo = Foo(a: 1, b: 2) let dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo)) print(dict)


No hay una forma directa de hacer esto en Codable. Debe implementar el protocolo codificable / decodificable para su estructura. Para su ejemplo, es posible que deba escribir de la siguiente manera

typealias EventDict = [String:Int] struct Favorite { var all:EventDict init(all: EventDict = [:]) { self.all = all } } extension Favorite: Encodable { struct FavoriteKey: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? { return nil } init?(intValue: Int) { return nil } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: FavoriteKey.self) for eventId in all { let nameKey = FavoriteKey(stringValue: eventId.key)! try container.encode(eventId.value, forKey: nameKey) } } } extension Favorite: Decodable { public init(from decoder: Decoder) throws { var events = EventDict() let container = try decoder.container(keyedBy: FavoriteKey.self) for key in container.allKeys { let fav = try container.decode(Int.self, forKey: key) events[key.stringValue] = fav } self.init(all: events) } }


No hay una forma integrada de hacer eso. Como se respondió anteriormente, si no tiene problemas de rendimiento, puede aceptar la JSONEncoder la JSONEncoder + JSONSerialization .

Pero prefiero seguir el camino de la biblioteca estándar para proporcionar un objeto codificador / decodificador.

class DictionaryEncoder { private let jsonEncoder = JSONEncoder() /// Encodes given Encodable value into an array or dictionary func encode<T>(_ value: T) throws -> Any where T: Encodable { let jsonData = try jsonEncoder.encode(value) return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) } } class DictionaryDecoder { private let jsonDecoder = JSONDecoder() /// Decodes given Decodable type from given array or dictionary func decode<T>(_ type: T.Type, from json: Any) throws -> T where T: Decodable { let jsonData = try JSONSerialization.data(withJSONObject: json, options: []) return try jsonDecoder.decode(type, from: jsonData) } }

Puedes probarlo con el siguiente código:

struct Computer: Codable { var owner: String? var cpuCores: Int var ram: Double } let computer = Computer(owner: "5keeve", cpuCores: 8, ram: 4) let dictionary = try! DictionaryEncoder().encode(computer) let decodedComputer = try! DictionaryDecoder().decode(Computer.self, from: dictionary)

Estoy intentando forzar aquí para acortar el ejemplo. En el código de producción, debe manejar los errores adecuadamente.


Si está utilizando SwiftyJSON , podría hacer algo como esto:

JSON(data: JSONEncoder().encode(foo)).dictionaryObject

Nota: También puede pasar este diccionario como parameters a las solicitudes de Alamofire .


Si no le importa cambiar un poco los datos, puede usar algo como esto:

extension Encodable { func asDictionary() throws -> [String: Any] { let data = try JSONEncoder().encode(self) guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else { throw NSError() } return dictionary } }

O una variante opcional

extension Encodable { var dictionary: [String: Any]? { guard let data = try? JSONEncoder().encode(self) else { return nil } return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] } } }

Suponiendo que Foo ajuste a Codable o realmente Encodable , puede hacer esto.

let struct = Foo(a: 1, b: 2) let dict = try struct.asDictionary() let optionalDict = struct.dictionary

Si desea ir hacia otro lado ( init(any) ), eche un vistazo a este Init un objeto conforme a Codable con un diccionario / matriz


let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]