Home > database >  Swift decode json when one property name / key is dynamic
Swift decode json when one property name / key is dynamic

Time:12-29

Json response from this call https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/usd/eur.min.json is pretty basic

{
  "date": "2022-12-27",
  "eur": 0.939751
}

first property is always named "date" and it's always String second is Double but it's name/key is dynamic, it can be "usd", "eur" etc.

I have tried

struct RateResponse: Decodable {

    let date: String
    let rate: Double

    enum CodingKeys: CodingKey {
        case date
    }

    init(from decoder: Decoder) throws {
        
        let container = try decoder.container(keyedBy: CodingKeys.self)
        date = try container.decode(String.self, forKey: CodingKeys.date)
        
        let singleValueContainer = try decoder.singleValueContainer()
        rate = try singleValueContainer.decode(Double.self)
    }
}

got this "Expected to decode Double but found a dictionary instead." I know what error says but not to sure how to solve it.

CodePudding user response:

My suggestion is a custom KeyDecodingStrategy.

First you need a neutral CodingKey struct to replace the currency key.

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

The RateResponse struct can be reduced to

struct RateResponse: Decodable {
    let date: String
    let rate: Double
}

The key decoding strategy passes the date key and replaces anything else with rate

let jsonString = """
{
  "date": "2022-12-27",
  "eur": 0.939751
}
"""

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .custom({
        let value = $0.last!.stringValue
        switch value {
            case "date":  return $0.last!
            default: return AnyKey(stringValue: "rate")!
        }
    })
    let result = try decoder.decode(RateResponse.self, from: Data(jsonString.utf8))
    print(result)
} catch {
    print(error)
}
  • Related