Swift Codable - Custom Decoding

Parse a JSON with dynamic keys and merge data from different depths

boaz frenkel
Cheetah Labs
Published in
2 min readFeb 2, 2020

--

Since Swift 4 I’m using the Codable protocol for parsing any JSON. With this powerful system for encoding and decoding types I’m enjoying all the niceties offered by the type-safe API provided by the compiler. This saves boilerplate code and the need to use third party dependencies.
Usually this fully-supported solution is easy to adopt and also provides the customization you need to encode and decode complex scenarios.
A common customization is the use of an enum to represent coding keys when they diverge from the Swift naming.

But, sometimes the data model used by a JSON file or API doesn’t match the model you’re using in an app. When that happens, you may need to merge or separate objects from the JSON when encoding and decoding.
Let’s look at one of these more custom decoding scenarios — a JSON with dynamic keys and different structure then our model.

Here is my model of a stock Quote:

and here is the server JSON response (based on the “alphavantage” API)

{
"2020-01-17 10:55:00": {
"1. open": "138.1700",
"2. high": "138.2800",
"3. low": "137.9700",
"4. close": "138.0295",
"5. volume": "135435"
},
"2020-01-17 10:50:00": {
"1. open": "138.2650",
"2. high": "138.2700",
"3. low": "138.1300",
"4. close": "138.1710",
"5. volume": "79366"
}
}

Notice that the date is a dynamic key that we don’t know it’s value, and we need it to be “merged” into the model (the time property of the Quote). The base for this is that we’re able to provide a custom ‘CodingKey’ implementation whenever necessary. Typically, this is done by using a String- or Int-backed enum.

Now for the magic — we create the structure of this response, set the keys we know and build the structure we want, and that is all we need.

That is it you can now use this simple command to parse the response:

let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(QuotesResponse.self, from: json)

Then you can access the array of quotes:

decodedResponse.quotes

This solution is for a specific need and if you have a better way I would love to know!

--

--