Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

312
Views
Swift: ¿enganchar en Decodable.init() para una clave no especificada?

Tengo algo de JSON que me gustaría decodificar con un JSONDecoder . El problema es que el nombre de una de las propiedades es útilmente dinámico cuando se envía desde el servidor.

Me gusta esto:

 { "someRandomName": [ [1,2,3], [4,5,6] ], "staticName": 12345 }

¿Cómo puedo decodificar esto, cuando someRandomName no se conoce en el momento de la compilación? He estado navegando a través de la www en busca de una respuesta, pero todavía no hay alegría. Realmente no puedo entender cómo funciona este Decodable , CodingKey . Algunos de los ejemplos tienen docenas de líneas, ¡y eso no parece correcto!

EDITAR Debo señalar que la clave se conoce en tiempo de ejecución, por lo que tal vez pueda pasarla al decodificar el objeto.

¿Hay alguna forma de conectarse a uno de los métodos o propiedades del protocolo para habilitar esta decodificación? No me importa si tengo que escribir un decodificador a medida solo para este objeto: todos los demás JSON están bien y son estándar.

EDITAR

Ok, mi entendimiento me ha llevado hasta aquí:

 struct Pair: Decodable { var pair: [[Double]] var last: Int private struct CodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } // Use for integer-keyed dictionary var intValue: Int? init?(intValue: Int) { // We are not using this, thus just return nil return nil } } init(from decoder: Decoder) throws { // just to stop the compiler moaning pair = [[]] last = 0 let container = try decoder.container(keyedBy: CodingKeys.self) // how do I generate the key for the correspond "pair" property here? for key in container.allKeys { last = try container.decode(Int.self, forKey: CodingKeys(stringValue: "last")!) pair = try container.decode([[Double]].self, forKey: CodingKeys(stringValue: key.stringValue)!) } } } init() { let jsonString = """ { "last": 123456, "XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ] } """ let jsonData = Data(jsonString.utf8) // this gives: "Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead.", underlyingError: nil))" let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData) dump(decodedResult) }

Así que ahora entiendo que la conformidad CodingKey está generando las claves para los datos serializados, no la estructura de Swift (que tiene mucho sentido ahora que lo pienso).

Entonces, ¿cómo genero ahora el caso para pair sobre la marcha, en lugar de codificarlo de esta manera? Sé que tiene algo que ver con el init(from decoder: Decoder) que necesito implementar, pero por mi vida no puedo entender cómo funciona eso. ¡Por favor ayuda!

EDITAR 2

Ok, estoy tan cerca ahora. La decodificación parece estar funcionando con esto:

 struct Pair: Decodable { var pair: [[Double]] var last: Int private enum CodingKeys : String, CodingKey { case last } private struct DynamicCodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } // Use for integer-keyed dictionary var intValue: Int? init?(intValue: Int) { // We are not using this, thus just return nil return nil } } init(from decoder: Decoder) throws { // just to stop the compiler moaning pair = [[]] last = 0 let container1 = try decoder.container(keyedBy: CodingKeys.self) last = try container1.decode(Int.self, forKey: .last) let container2 = try decoder.container(keyedBy: DynamicCodingKeys.self) for key in container2.allKeys { pair = try container2.decode([[Double]].self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!) } } }

Este código parece hacer su trabajo: examinar las propiedades last y pair en la función en sí y se ve bien; pero recibo un error al intentar decodificar:

 init() { let jsonString = """ { "last": 123456, "XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ] } """ let jsonData = Data(jsonString.utf8) // Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [DynamicCodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead." let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData) dump(decodedResult) }

Estoy tan cerca que puedo saborearlo...

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

Está buscando JSONSerializer no JSONDecoder , supongo, https://developer.apple.com/documentation/foundation/jsonserialization .

Debido a que la clave es impredecible, es mejor convertirla a Dictionary . O puede echar un vistazo a este https://swiftsenpai.com/swift/decode-dynamic-keys-json/

over 4 years ago · Santiago Trujillo Report

0

Si la clave dinámica se conoce en tiempo de ejecución, puede pasarla a través del diccionario de información de userInfo del decodificador.

En primer lugar crea dos extensiones.

 extension CodingUserInfoKey { static let dynamicKey = CodingUserInfoKey(rawValue: "dynamicKey")! } extension JSONDecoder { convenience init(dynamicKey: String) { self.init() self.userInfo[.dynamicKey] = dynamicKey } }

En la estructura, implemente CodingKeys como estructura para poder crear claves sobre la marcha.

 struct Pair : Decodable { let last : Int let pair : [[Double]] private struct CodingKeys: CodingKey { var intValue: Int? var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } static let last = CodingKeys(stringValue: "last")! static func makeKey(name: String) -> CodingKeys { return CodingKeys(stringValue: name)! } } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) guard let dynamicKey = decoder.userInfo[.dynamicKey] as? String else { throw DecodingError.dataCorruptedError(forKey: .makeKey(name: "pair"), in: container, debugDescription: "Dynamic key in userInfo is missing") } last = try container.decode(Int.self, forKey: .last) pair = try container.decode([[Double]].self, forKey: .makeKey(name: dynamicKey)) } }

Ahora cree el JSONDecoder pasando el nombre dinámico conocido

 let jsonString = """ { "last": 123456, "XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ] } """ do { let decoder = JSONDecoder(dynamicKey: "XBTUSD") let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8)) print(result) } catch { print(error) }

Editar:

Si el JSON contiene siempre solo dos claves, este es un enfoque más fácil:

 struct AnyKey: CodingKey { var stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } } struct Pair : Decodable { let last : Int let pair : [[Double]] } let jsonString = """ { "last": 123456, "XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ] } """ do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .custom({ codingPath in let lastPath = codingPath.last! if lastPath.stringValue == "last" { return lastPath } return AnyKey(stringValue: "pair")! }) let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8)) print(result) } catch { print(error) }
over 4 years ago · Santiago Trujillo Report

0

¡Ahora tengo un código que realmente funciona!

 struct Pair: Decodable { var pair: [[Double]] var last: Int private struct CodingKeys: CodingKey { var intValue: Int? var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } static let last = CodingKeys(stringValue: "last")! static func makeKey(name: String) -> CodingKeys { return CodingKeys(stringValue: name)! } } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) last = try container.decode(Int.self, forKey: .last) let key = container.allKeys.first(where: { $0.stringValue != "last" } )?.stringValue pair = try container.decode([[Double]].self, forKey: .makeKey(name: key!)) } } init() { let jsonString = """ { "last": 123456, "XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ] } """ let jsonData = Data(jsonString.utf8) // Ask JSONDecoder to decode the JSON data as DecodedArray let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData) dump(decodedResult) }
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!