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"
*/