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