Home > Software design >  Translate a json dictionary object to Array of objects
Translate a json dictionary object to Array of objects

Time:04-19

I am getting back JSON that looks like this:

{
  "success":true,
  "timestamp":1650287883,
  "base":"EUR",
  "date":"2022-04-18",
  "rates":{
    "USD":1.080065,
    "EUR":1,
    "JPY":136.717309,
    "GBP":0.828707,
    "AUD":1.465437,
    "CAD":1.363857
  }
}

I was expecting rates to be an array, but it's an object. The currency codes may vary. Is there a way to Decode this with Swift's built-in tools?

I'm certain this won't work:

struct ExchangeRateResponse: Decodable {
    let success: Bool
    let base: String
    let date: Date
    let rates: [[String: Double]]

    private enum ResponseKey: String, CodingKey {
        case success, base, date, rates
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ResponseKey.self)

        success = try container.decode(Bool.self, forKey: .success)
        base = try container.decode(String.self, forKey: .base)
        date = try container.decode(Date.self, forKey: .date)
        rates = try container.decode([[String: Double]].self, forKey: .rates)
    }
}

CodePudding user response:

your model is wrong. you can do this:

struct YourModelName {
    let success: Bool
    let timestamp: Int
    let base, date: String
    let rates: [String: Double]
}

after that you can try do decode it. something like this:

do {
   let jsonDecoder = JSONDecoder()
   let loadData = try jsonDecoder.decode(YourModelName.self, from: data!)
   // 'loadData' is your data that you want. for your problem you have to use 'loadData.rates'. Hint: you have to use it in 'for' loop!
   DispatchQueue.main.async { _ in
       // if you have to update your UI
   }
} catch {
    print(error)
}

CodePudding user response:

First of all you cannot decode date to Date out of the box, but you can decode timestamp to Date.

Second of all it's impossible to decode a dictionary to an array. This is like apples and oranges.

But fortunately you can map the dictionary to an array because it behaves like an array (of tuples) when being mapped.

Just create an other struct Rate

struct Rate {
    let code: String
    let value: Double
}

struct ExchangeRateResponse: Decodable {
    let success: Bool
    let timestamp: Date
    let base: String
    let date: String
    let rates: [Rate]

    private enum CodingKeys: String, CodingKey {
        case success, base, timestamp, date, rates
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        success = try container.decode(Bool.self, forKey: .success)
        timestamp = try container.decode(Date.self, forKey: .timestamp)
        base = try container.decode(String.self, forKey: .base)
        date = try container.decode(String.self, forKey: .date)

        let rateData = try container.decode([String: Double].self, forKey: .rates)
        rates = rateData.map(Rate.init)
    }
}

do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    let result = try decoder.decode(ExchangeRateResponse.self, from: data)
    print(result)
} catch {
    print(error)
}

Or still shorter if you map the dictionary after decoding the stuff

struct ExchangeRateResponse: Decodable {
    let success: Bool
    let timestamp: Date
    let base: String
    let date: String
    let rates: [String:Double]
}

do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    let result = try decoder.decode(ExchangeRateResponse.self, from: data)
    let rates = result.rates.map(Rate.init)
    print(rates)
} catch {
    print(error)
}
  • Related