Search code examples
jsonswiftjsondecoder

How to decode JSON in Swift?


I have an API JSON response like below. I want to decode the JSON to get an array of dictionary [String:Double], like [{"2020-01-01" : 0.891186}, {"2020-01-02" : 0.891186}].

{
    "rates": {
        "2020-01-01": {
            "EUR": 0.891186
        },
        "2020-01-02": {
            "EUR": 0.891186
        },
        "2020-01-03": {
            "EUR": 0.895175
        },
        "2020-01-04": {
            "EUR": 0.895175
        }
    }
}

I have written decode code like below:

do {
            let data = try Data(contentsOf: appURL)
            let decoder = JSONDecoder()
            let response = try decoder.decode(Rates.self, from: data)
            response.rates
        } catch let jsonError {
            print(jsonError)
        }

And I have tried to define a struct:

struct Rates: Codable, Hashable {
    let rates: Point
}

struct Point {

}

But I don't have an idea about what I should write in struct Point because the date is not a consistent field.


Solution

  • Here are two possible solutions, one with the struct Point and the other one with a dictionary instead

    First the solution with Point

    struct Point: Codable {
        let date: String
        let rate: Double
    }
    

    And then create a custom init(from:) where we decode the json into a dictionary first, [String: [String: Double]] and then map that dictionary into an array of Point

    struct Rates: Codable {
        let rates: [Point]
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let dictionary = try container.decode([String: [String: Double]].self, forKey: .rates)
            rates = dictionary.map { Point(date: $0.key, rate: $0.value.first?.value ?? .zero) }
        }
    }
    

    And here is the second solution using a dictionary

    struct Rates: Codable {
        let rates: [String: Double]
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let dictionary = try container.decode([String: [String: Double]].self, forKey: .rates)
            rates = dictionary.compactMapValues { $0.first?.value }
        }
    }