Search code examples
jsonswiftswift4codablejsondecoder

How to decode variable from json when key is changing according to user input?


I am trying to parse some JSON response coming from CoinmarketCap using the JSONDecoder() in Swift 4. But the problem is that the response from json is changing according to user input. e.g if user wants the price in eur, the output is following:

[
    {
        "price_eur": "9022.9695444"
    }
]

but if user wants the price in gbp:

[
    {
        "price_gbp": "7906.8032145"
    }
]

So the question is how should I make the struct that inherits from Decodable if the variable(json key) name is changing?


Solution

  • You can decode the dynamic key by creating a custom init(from:) method for your struct, then using two set of coding keys, an enum containing all keys that are known at compile time and another struct that you initialize using the dynamic keys that are generated using user input (contain the name of the currency).

    In your custom init(from:) method you just need to decode each property using their respective keys.

    let chosenCurrency = "gbp"
    
    struct CurrencyResponse: Decodable {
        let name:String
        let symbol:String
        let price:String
        private static var priceKey:String {
            return "price_\(chosenCurrency)"
        }
    
        private enum SimpleCodingKeys: String, CodingKey {
            case name, symbol
        }
    
        private struct PriceCodingKey : CodingKey {
            var stringValue: String
            init?(stringValue: String) {
                self.stringValue = stringValue
            }
            var intValue: Int?
            init?(intValue: Int) {
                return nil
            }
        }
    
        init(from decoder:Decoder) throws {
            let values = try decoder.container(keyedBy: SimpleCodingKeys.self)
            name = try values.decode(String.self, forKey: .name)
            symbol = try values.decode(String.self, forKey: .symbol)
            let priceValue = try decoder.container(keyedBy: PriceCodingKey.self)
            price = try priceValue.decode(String.self, forKey: PriceCodingKey(stringValue:CurrencyResponse.priceKey)!)
        }
    }
    
    do {
        let cryptoCurrencies = try JSONDecoder().decode([CurrencyResponse].self, from: priceJSON.data(using: .utf8)!)
    } catch {
        print(error)
    }
    

    Test JSON:

    let priceJSON = """
        [
        {
        "id": "bitcoin",
        "name": "Bitcoin",
        "symbol": "BTC",
        "rank": "1",
        "price_\(chosenCurrency)": "573.137",
        "price_btc": "1.0",
        "24h_volume_\(chosenCurrency)": "72855700.0",
        "market_cap_\(chosenCurrency)": "9080883500.0",
        "available_supply": "15844176.0",
        "total_supply": "15844176.0",
        "percent_change_1h": "0.04",
        "percent_change_24h": "-0.3",
        "percent_change_7d": "-0.57",
        "last_updated": "1472762067"
        },
        {
        "id": "ethereum",
        "name": "Ethereum",
        "symbol": "ETH",
        "rank": "2",
        "price_\(chosenCurrency)": "12.1844",
        "price_btc": "0.021262",
        "24h_volume_\(chosenCurrency)": "24085900.0",
        "market_cap_\(chosenCurrency)": "1018098455.0",
        "available_supply": "83557537.0",
        "total_supply": "83557537.0",
        "percent_change_1h": "-0.58",
        "percent_change_24h": "6.34",
        "percent_change_7d": "8.59",
        "last_updated": "1472762062"
    }
    ]
    """