Using Codable with Nested JSON? You Can!
Nesting? Don’t worry!
Still using several structs to decode data that is in a nested JSON String? Well, no longer! This article is here to help you out.
Difficulty: Beginner | Easy | Normal | Challenging
The JSON String
The following JSON response is meant to represent a user. Now this is a common construct, and as we can see has a nested
name within the
The poor solution
A common (but poor) solution for this is to produce objects of type
struct and decode. The separate objects are…not ideal:
This can then be tested (of course if you are following along in a Playground you’ll need to
import XCTest and also
There’s a further problem: User doesn’t conform to Equatable so I’ll whack this into an extension.
What do you know?
A Fully passing test. But did you know that things can be more awesome?
The awesome solution
The Model will be changed (slightly) in name to make this easier to read on a single Playground so is called
UserModel for this section.
This is slightly more tricky than the approach above, as the CodingKeys need to be specified individually.
In this example we have four variables (
last) with all of them containing Strings.
The top-level coding keys will be Gender (because it is, as expected at the top-level of the JSON String). The object
name is also defined as one of the case statements (despite not having a variable to match). The next-level of coding keys are the next level keys from the JSON string (title, first and last).
This gives us a start for the UserModel:
Now we need to conform to the
Decodable and the
As far as Decodable is concerned, we provide a custom initializer. The Top-level coding keys har handled first.
The easiest is
gender which simply operates through a top-level container
let container = try decoder.container(keyedBy: CodingKeys.self), and gender is created through
gender = try container.decode(String.self, forKey: .gender) which works through the
enum stated above (and of course is decoded as a String).
We know that
name is also at the top level, but has a key from the name
let name = try container.nestedContainer(keyedBy: NameKeys.self, forKey: .name) — but effectively this is just a second container that then enables us to populate the second-tier variables:
first = try name.decode(String.self, forKey: .first)
last = try name.decode(String.self, forKey: .last)
title = try name.decode(String.self, forKey: .title)
As a whole this is coded as:
For Encodable we once define the top level and second-level variables should be encoded. Again
name is effectively a second container (the type of name is actually
The code for this is given below:
Testing this is known as a very tricky process. But don’t worry; I’ve got you covered. But in order to do so we need to compare
UserModel instances; to do so we conform to the
Equatable protocol (with empty brackets as this already conforms):
The we create a test that encodes and decodes the string (an unchanged string from the start of this article). The code for this is given below:
The full gist
It actually is quite tricky to deal with nested JSON statements. Actually that isn’t true, doing it in the less good way is easy. Producing multiple
structs to simply deal with nested JSON isn’t on though, and perhaps shouldn’t pass that code review over at your place.
Indeed: I hope this article does help you out in creating great software that is both great for your user and even great for that code review by using better techniques.
If you’ve got questions, please do get in touch over on Twitter and I’ll do everything I can to help you out.
Extend your knowledge
- Apple have a guide, which goes so far as to having custom encoding and decoding protocols