JSON data is rarely flat and often contains nested objects.
Nested JSON data can be decoded in Swift using the Decodable
protocol and the JSONDecoder
class.
However, how you decode nested JSON objects depends on the final result you want to obtain.
- This article is part of the Parsing JSON in Swift series.
FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet
Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development
DOWNLOAD THE FREE GUIDETable of contents
Decoding nested JSON objects using nested Swift types
The easiest way to decode nested JSON objects is to create equivalent Decodable
Swift types that follow the nesting of the JSON data.
This is the most straightforward approach, and it works with single JSON objects and arrays.
import Foundation
let json = """
{
"title": "Inception",
"year": 2010,
"director": {
"name": "Christopher",
"surname": "Nolan"
},
"cast": [
{
"name": "Leonardo",
"surname": "DiCaprio"
},
{
"name": "Cillian",
"surname": "Murphy"
},
{
"name": "Michael",
"surname": "Caine"
}
]
}
""".data(using: .utf8)!
struct Movie: Decodable {
let title: String
let year: Int
let director: Person
let cast: [Person]
}
struct Person: Decodable {
let name: String
let surname: String
}
In the example above, the Person
structure decodes the data of a movie’s director and cast.
Flattening single JSON objects using a nested keyed container
The standard approach may produce several Swift types that are not needed by your app and are only required for decoding the JSON data.
In such cases, it is better to flatten the nested JSON objects into a single Swift type containing all the needed values. For that, you need a decoding initializer with custom flattening logic.
In a decoding initializer, you decode the properties of a JSON object individually using a KeyedDecodingContainer keyed by your type’s coding keys. You get this container from the decoder by calling its nestedContainer(keyedBy:forKey:)
method.
Similarly, you can obtain another KeyedDecodingContainer
from the primary container to decode a nested JSON object by calling its nestedContainer(keyedBy:forKey:)
. This allows you to avoid creating full-fledged nested Swift type.
For example, our app might display the movie director’s name as a single String
, so there is no need to declare a Person
type for the director
property of the Movie
structure.
struct Movie: Decodable {
let title: String
let year: Int
let director: String
let cast: [Person]
enum CodingKeys: CodingKey {
case title
case year
case director
case cast
}
enum PersonKeys: CodingKey {
case name
case surname
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.year = try container.decode(Int.self, forKey: .year)
self.cast = try container.decode([Person].self, forKey: .cast)
let directorContainer = try container.nestedContainer(
keyedBy: PersonKeys.self,
forKey: .director
)
let name = try directorContainer.decode(String.self, forKey: .name)
let surname = try directorContainer.decode(String.self, forKey: .surname)
self.director = name + " " + surname
}
}
Notice that you must also create coding keys for the nested JSON object.
You also use a KeyedDecodingContainer
to decode JSON with dynamic keys. However, in that case, you also need to complement it with a custom coding key structure.
Flattening arrays of JSON objects using a nested unkeyed container
The KeyedDecodingContainer
works only for JSON objects, which can be keyed using coding keys. That does not work for arrays, so you must use an UnkeyedDecodingContainer instead.
Similarly to the code above, you get an UnkeyedDecodingContainer
for a nested array of objects by calling the nestedUnkeyedContainer(forKey:)
method on the main container.
Then, you iterate over the JSON objects inside the array and decode them using another KeyedDecodingContainer
, which you get from the UnkeyedDecodingContainer
.
For example, we can use this approach to flatten the cast’s names inside our Movie
structure into a simple array of strings instead of an array of Person
values.
struct Movie: Decodable {
let title: String
let year: Int
let director: String
let cast: [String]
enum CodingKeys: CodingKey {
case title
case year
case director
case cast
}
enum PersonKeys: CodingKey {
case name
case surname
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// ...
var castContainer = try container.nestedUnkeyedContainer(forKey: .cast)
var cast: [String] = []
while !castContainer.isAtEnd {
let actorContainer = try castContainer.nestedContainer(keyedBy: PersonKeys.self)
let name = try actorContainer.decode(String.self, forKey: .name)
let surname = try actorContainer.decode(String.self, forKey: .surname)
cast.append(name + " " + surname)
}
self.cast = cast
}
}
Notice that the UnkeyedDecodingContainer
advances to the next object inside the array every time we call its nestedContainer(keyedBy:forKey:)
method. All we need to do is to check its isAtEnd
property in a while
loop to detect when the iteration has reached the end of the array.
You use an UnkeyedDecodingContainer
also when iterating over arrays of JSON objects with dynamic types. However, in that case, you need to first use a KeyedDecodingContainer
to read the dynamic type of the object you are decoding.
If you want a quick reference of how to flatten nested JSON data in Swift, together with other JSON parsing techniques, download my free cheat sheet below.
FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet
Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development
DOWNLOAD THE FREE GUIDEMatteo has been developing apps for iOS since 2008. He has been teaching iOS development best practices to hundreds of students since 2015 and he is the developer of Vulcan, a macOS app to generate SwiftUI code. Before that he was a freelance iOS developer for small and big clients, including TomTom, Squla, Siilo, and Layar. Matteo got a master’s degree in computer science and computational logic at the University of Turin. In his spare time he dances and teaches tango.