I'm trying to decode the following JSON string using Codables
in Swift.
let json = """
{
"todaysProvision": 235.50,
"referenceDate": "2019-01-30",
"lastSevenDays": {
"2019-02-12": 235.20,
"2019-02-11": 235.20,
"2019-02-10": 235.20,
"2019-02-09": 235.20,
"2019-02-08": 235.20,
"2019-02-07": 235.20,
"2019-02-06": 235.20,
}
}
"""
My current struct for decoding looks like this.
struct ProvisionInfo: Codable {
let todaysProvision: Double
let referenceDate: Date
}
Using JSONDecoder
with dateDecodingStrategy
works like a charm for the key referenceDate
:
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(df)
let decoded = try decoder.decode(ProvisionInfo.self, from: json.data(using: .utf8)!)
// decoded.todaysProvision -> 235.5
// decoded.referenceDate -> "Jan 30, 2019 at 12:00 AM"
For the JSON dictionary lastSevenDays
I thought, I could simply change my struct to
struct ProvisionInfo: Codable {
let todaysProvision: Double
let referenceDate: Date
let lastSevenDays: [Date: Double]
}
But with that i got decoding errors
Playground execution terminated: An error was thrown and was not caught:
▿ DecodingError
▿ typeMismatch : 2 elements
- .0 : Swift.Array<Any>
▿ .1 : Context
▿ codingPath : 1 element
- 0 : CodingKeys(stringValue: "lastSevenDays", intValue: nil)
- debugDescription : "Expected to decode Array<Any> but found a dictionary instead."
- underlyingError : nil
The only way to solve this problem was to add a custom initializer for decoding my struct, which parses the dictionary as [String: Double]
and converts that dictionary into [Date: Double]
, using the same DateFormatter
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
todaysProvision = try container.decode(Double.self, forKey: .todaysProvision)
referenceDate = try container.decode(Date.self, forKey: .referenceDate)
let foo = try container.decode([String: Double].self, forKey: .lastSevenDays)
lastSevenDays = Dictionary(uniqueKeysWithValues:foo.compactMap { (key, value) in
guard let date = df.date(from: key) else { return nil }
return (date, value)
})
}
Is this the only possible way to parse [Date: Double]
using Codables
or am I doing something wrong?
Thanks!