Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

313
Visualizações
Swift: hook into Decodable.init() for an unspecified key?

I have some JSON I would like to decode with a JSONDecoder. Trouble is, the name of one of the properties is helpfully dynamic when sent from the server.

Like this:

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

How can I decode this, when the someRandomName is not known at build time? I have been trawling through the www looking for an answer, but still no joy. Can't really get my head around how this Decodable, CodingKey stuff works. Some of the examples are dozens of lines long, and that doesn't seem right!

EDIT I should point out that the key is known at runtime, so perhaps I can pass it in when decoding the object?

Is there any way to hook into one of the protocol methods or properties to enable this decoding? I don't mind if I have to write a bespoke decoder for just this object: all the other JSON is fine and standard.

EDIT

Ok, my understanding has taken me this far:

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

So I now understand that the CodingKey conformance is generating the keys for the serialized data, not the Swift struct (which kinda makes perfect sense now I think about it).

So how do I now generate the case for pair on the fly, rather than hard-coding it like this? I know it has something to do with the init(from decoder: Decoder) I need to implement, but for the life of me I can't work out how that functions. Please help!

EDIT 2

Ok, I'm so close now. The decoding seems to be working with this:

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

This code seems to do its job: examining the last and pair properties in the function itself and it looks good; but I'm getting an error when trying to decode:

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

I'm so close I can taste it...

over 4 years ago · Santiago Trujillo
3 Respostas
Responde à pergunta

0

You're looking for JSONSerializer not JSONDecoder I guess, https://developer.apple.com/documentation/foundation/jsonserialization.

Because the key is unpredictable, so better convert to Dictionary. Or you can take a look at this https://swiftsenpai.com/swift/decode-dynamic-keys-json/

over 4 years ago · Santiago Trujillo Relatório

0

If the dynamic key is known at runtime, you can pass it via the userInfo dictionary of the decoder.

First of all create two extensions

extension CodingUserInfoKey {
    static let dynamicKey = CodingUserInfoKey(rawValue: "dynamicKey")!
}

extension JSONDecoder {
    convenience init(dynamicKey: String) {
        self.init()
        self.userInfo[.dynamicKey] = dynamicKey
    }
}

In the struct implement CodingKeys as struct to be able to create keys on the fly.

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

Now create the JSONDecoder passing the known dynamic name

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

Edit:

If the JSON contains always only two keys this is an easier approach:

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 Relatório

0

I now have some code that actually works!

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 Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda