Edison
Edison

Reputation: 11987

Iterating through dictionary keys of decoded JSON in Swift

I received excellent help in my previous question on how to set up my foundational JSON model. I am able to parse any values I want.

While I can parse any values I want, I am only able to access symbols or other values separately using dot notation.
btcSymbol = rawResponse.btc?.symbol
ethSymbol = rawResponse.eth?.symbol

I found other questions about iterating through dictionaries like Iterating Through a Dictionary in Swift but these examples are basic arrays and not multi-nested dictionaries using Swift's new protocols.

I want to be able to:
1. Iterate through the JSON and extract only the symbols from the CMC API.
2. Have a model where I am able to iterate all values of each currency separately so that I can later send those values to a table view for example.
BTC | name | symbol | marketCap | MaxSupply
ETH | name | symbol | marketCap | MaxSupply

Would restructuring my already existing model be the best solution? After my model is built would a standard for in loop or map be better?

JSONModel

struct RawServerResponse : Codable {
enum Keys : String, CodingKey {
    case data = "data"
}
let data : [String:Base]
}

struct Base : Codable {
enum CodingKeys : String, CodingKey {
    case id = "id"
    case name = "name"
    case symbol = "symbol"
}

let id : Int64
let name : String
let symbol : String
}

struct Quote : Codable {
enum CodingKeys : String, CodingKey {
    case price = "price"
    case marketCap = "market_cap"
}

let price :  Double
let marketCap : Double
}

extension RawServerResponse {
enum BaseKeys : String {
    case btc = "1"
    case eth = "1027"      
}
var btc : Base? { return data[BaseKeys.btc.rawValue] }
var eth : Base? { return data[BaseKeys.eth.rawValue] }

}

extension Base {
enum Currencies : String {
    case usd = "USD"
}
var usd : Quote? { return quotes[Currencies.usd.rawValue]}
}

struct ServerResponse: Codable {
let btcName: String?
let btcSymbol: String?

init(from decoder: Decoder) throws {
    let rawResponse = try RawServerResponse(from: decoder)

    btcSymbol = rawResponse.btc?.symbol

JSON

{
"data": {
    "1": {
        "id": 1, 
        "name": "Bitcoin", 
        "symbol": "BTC", 
        "website_slug": "bitcoin", 
        "rank": 1, 
        "circulating_supply": 17041575.0, 
        "total_supply": 17041575.0, 
        "max_supply": 21000000.0, 
        "quotes": {
            "USD": {
                "price": 8214.7, 
                "volume_24h": 5473430000.0, 
                "market_cap": 139991426153.0, 
                "percent_change_1h": 0.09, 
                "percent_change_24h": 2.29, 
                "percent_change_7d": -2.44
            }
        }
    }

Upvotes: 2

Views: 1907

Answers (1)

vadian
vadian

Reputation: 285150

At least I'd recommend to map the data dictionary to get the symbol as key rather than the id, by the way if the keys are camelCaseable and you pass the .convertFromSnakeCase key decoding strategy you don't need any coding keys, for example

struct RawServerResponse : Codable {
    var data = [String:Base]()

    private enum CodingKeys: String, CodingKey { case data }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let baseDictionary = try container.decode([String:Base].self, forKey: .data)
        baseDictionary.forEach { data[$0.1.symbol] = $0.1 }
    }
}

struct Base : Codable {
    let id : Int64
    let name : String
    let symbol : String
    let quotes : [String:Quote]
}

struct Quote : Codable {
    let price : Double
    let marketCap : Double
}

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let rawResponse = try decoder.decode(RawServerResponse.self, from: data)
    for (symbol, base) in rawResponse.data {
        print(symbol, base.quotes["USD"]?.marketCap)
        // ETH Optional(68660795252.0)
        // BTC Optional(139991426153.0)
    }
} catch { print(error) }

Upvotes: 3

Related Questions