serialize keys encodable coding swift codable

keys - string to json swift 4



Swift 4 Decodable-Diccionario con enum como clave (2)

El problema es que la conformidad Codable Dictionary actualmente solo puede manejar correctamente las String e Int . Para un diccionario con cualquier otro tipo de Key (donde esa Key es Encodable / Encodable ), se codifica y decodifica con un contenedor sin clave (matriz JSON) con valores de clave alternativos.

Por lo tanto al intentar decodificar el JSON:

{"dictionary": {"enumValue": "someString"}}

en AStruct , se AStruct el valor de la clave "dictionary" sea ​​una matriz.

Asi que,

let jsonDict = ["dictionary": ["enumValue", "someString"]]

Funcionaría, dando el JSON:

{"dictionary": ["enumValue", "someString"]}

que luego se descodificaría en:

AStruct(dictionary: [AnEnum.enumValue: "someString"])

Sin embargo, realmente creo que la conformidad Codable Dictionary debería poder manejar adecuadamente cualquier tipo de CodingKey sea CodingKey con CodingKey como su Key (que AnEnum puede ser), ya que puede codificar y decodificar en un contenedor con clave con esa clave (no dude en archiva un error solicitando para esto).

Hasta que se implemente (si es que se aplica), siempre podríamos crear un tipo de envoltorio para hacer esto:

struct CodableDictionary<Key : Hashable, Value : Codable> : Codable where Key : CodingKey { let decoded: [Key: Value] init(_ decoded: [Key: Value]) { self.decoded = decoded } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Key.self) decoded = Dictionary(uniqueKeysWithValues: try container.allKeys.lazy.map { (key: $0, value: try container.decode(Value.self, forKey: $0)) } ) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Key.self) for (key, value) in decoded { try container.encode(value, forKey: key) } } }

y luego implementar como tal:

enum AnEnum : String, CodingKey { case enumValue } struct AStruct: Codable { let dictionary: [AnEnum: String] private enum CodingKeys : CodingKey { case dictionary } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) dictionary = try container.decode(CodableDictionary.self, forKey: .dictionary).decoded } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(CodableDictionary(dictionary), forKey: .dictionary) } }

(o simplemente tenga la propiedad de dictionary del tipo CodableDictionary<AnEnum, String> y use la conformidad Codable generada Codable , luego hable en términos de dictionary.decoded )

Ahora podemos decodificar el objeto JSON anidado como se espera:

let data = """ {"dictionary": {"enumValue": "someString"}} """.data(using: .utf8)! let decoder = JSONDecoder() do { let result = try decoder.decode(AStruct.self, from: data) print(result) } catch { print(error) } // AStruct(dictionary: [AnEnum.enumValue: "someString"])

Si bien todo esto se dice, se podría argumentar que todo lo que se está logrando con un diccionario con una enum como clave es solo una struct con propiedades opcionales (y si espera que un valor determinado siempre esté allí; haga que no sea opcional. ).

Por lo tanto, es posible que desee que su modelo se vea como:

struct BStruct : Codable { var enumValue: String? } struct AStruct: Codable { private enum CodingKeys : String, CodingKey { case bStruct = "dictionary" } let bStruct: BStruct }

Lo que funcionaría bien con tu JSON actual:

let data = """ {"dictionary": {"enumValue": "someString"}} """.data(using: .utf8)! let decoder = JSONDecoder() do { let result = try decoder.decode(AStruct.self, from: data) print(result) } catch { print(error) } // AStruct(bStruct: BStruct(enumValue: Optional("someString")))

Mi estructura de datos tiene una enumeración como clave, esperaría que la siguiente información se decodifique automáticamente. ¿Es este un error o algún problema de configuración?

import Foundation enum AnEnum: String, Codable { case enumValue } struct AStruct: Codable { let dictionary: [AnEnum: String] } let jsonDict = ["dictionary": ["enumValue": "someString"]] let data = try! JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted) let decoder = JSONDecoder() do { try decoder.decode(AStruct.self, from: data) } catch { print(error) }

El error que recibo es este, parece confundir el dict con una matriz.

typeMismatch (Swift.Array, Swift.DecodingError.Context (codingPath: [Opcional (__ lldb_expr_85.AStruct. (CodingKeys in _0E2FD0A9B523101VDDD6678FFXD1DD))


Para resolver su problema, puede usar uno de los dos siguientes fragmentos de código del Patio de Recreo.

# 1. Usando el init(from:) Decodable

import Foundation enum AnEnum: String, Codable { case enumValue } struct AStruct { enum CodingKeys: String, CodingKey { case dictionary } enum EnumKeys: String, CodingKey { case enumValue } let dictionary: [AnEnum: String] } extension AStruct: Decodable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dictContainer = try container.nestedContainer(keyedBy: EnumKeys.self, forKey: .dictionary) var dictionary = [AnEnum: String]() for enumKey in dictContainer.allKeys { guard let anEnum = AnEnum(rawValue: enumKey.rawValue) else { let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to an AnEnum object") throw DecodingError.dataCorrupted(context) } let value = try dictContainer.decode(String.self, forKey: enumKey) dictionary[anEnum] = value } self.dictionary = dictionary } }

Uso:

let jsonString = """ { "dictionary" : { "enumValue" : "someString" } } """ let data = jsonString.data(using: String.Encoding.utf8)! let decoder = JSONDecoder() let aStruct = try! decoder.decode(AStruct.self, from: data) dump(aStruct) /* prints: ▿ __lldb_expr_148.AStruct ▿ dictionary: 1 key/value pair ▿ (2 elements) - key: __lldb_expr_148.AnEnum.enumValue - value: "someString" */

# 2. Uso del KeyedDecodingContainerProtocol de decode(_:forKey:)

import Foundation public enum AnEnum: String, Codable { case enumValue } struct AStruct: Decodable { enum CodingKeys: String, CodingKey { case dictionary } let dictionary: [AnEnum: String] } public extension KeyedDecodingContainer { public func decode(_ type: [AnEnum: String].Type, forKey key: Key) throws -> [AnEnum: String] { let stringDictionary = try self.decode([String: String].self, forKey: key) var dictionary = [AnEnum: String]() for (key, value) in stringDictionary { guard let anEnum = AnEnum(rawValue: key) else { let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to an AnEnum object") throw DecodingError.dataCorrupted(context) } dictionary[anEnum] = value } return dictionary } }

Uso:

let jsonString = """ { "dictionary" : { "enumValue" : "someString" } } """ let data = jsonString.data(using: String.Encoding.utf8)! let decoder = JSONDecoder() let aStruct = try! decoder.decode(AStruct.self, from: data) dump(aStruct) /* prints: ▿ __lldb_expr_148.AStruct ▿ dictionary: 1 key/value pair ▿ (2 elements) - key: __lldb_expr_148.AnEnum.enumValue - value: "someString" */