Hello my fellow sorcerers and witches, Leo here. The topic today is how Handling API Response Hierarchy in Swift is important.

Today we’ll explore some parsing magic that you can do to handle the way your API deal with JSON. It’s an important topic because oftentimes your backend APIs will send you data that isn’t much friendly to deal with, this leads to misunderstandings and error-prone codes. The big picture here is to remove OR add complexity to our parsed JSON data as we wish.

Let’s stop talking and do some magic decoder parsing.

 

The problem

You receive a JSON with X hierarchy and you want to parse it to another hierarchy structure.

First, let us visualize the first JSON response we’re getting from the server:

let houseJson = """
{
"height": 10,
"width": 20,
"color": "red",
"address" : {
"streetName": "Holy Street",
"houseNumber": 42
}
}

""".data(using: .utf8)!

Notice that we have two hierarchy levels in this JSON. The first with the keys height, width, color, and address. And the second one with streetName and the houseNumber. Now let’s suppose that your goal is to flatten this hierarchy, resulting in this kind of structure:

let houseJson = """
{
"height": 10,
"width": 20,
"color": "red",
"streetName": "Holy Street",
"houseNumber": 42
}

""".data(using: .utf8)!

We’ll take advantage of the Swift Decodable to handle this. We will create a custom decodable to do the heavy lifting for us:

struct House: Decodable { //1
    let height: Int
    let width: Int
    let color: String
    let streetName: String
    let houseNumber: Int
    
    enum CodingKeys: String, CodingKey {
        case height
        case width
        case color
        case address //2
    }
    
    enum AddressCodingKeys: String, CodingKey { //3
        case streetName
        case houseNumber
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self) //4
        height = try container.decode(Int.self, forKey: .height)
        width = try container.decode(Int.self, forKey: .width)
        color = try container.decode(String.self, forKey: .color)
        
        let houseContainer = try container.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address) // 5
        
        streetName = try houseContainer.decode(String.self, forKey: .streetName)
        houseNumber = try houseContainer.decode(Int.self, forKey: .houseNumber)
        
    }
}

Let’s analyze each data point. At 1 mark we conform the struct with the Decodable protocol this ensures that we can use the struct to decode the JSON data and we already set the final properties that we want, therefore the Struct won’t have the Address object, it already has the flattened structure on his properties.

The 2 mark is important because we will use that to get a link to AddressCodingKeys nested container that we’ll check later. At 3 mark we are creating the nested CodingKey enum representing the nested hierarchy, this is important because when we build the init function we’ll use it to parse the data.

The init method is where the magic began and things get interesting. First, on the 4 mark, we get the outer container and get the keys we want from it, in this example the height, width, and color. And finally, at the 5 mark, we get the nested type container and we decode the last keys for the properties that we want.

It’s pretty simple, right?

And to test it you can use this in your playground:

let decoder = JSONDecoder()
let house = try! decoder.decode(House.self, from: houseJson)
print(house)

You will see this result:

Handling API Response Hierarchy in Swift first response image showing the api response

 

Quick Tip on Handling API response hierarchy in Swift

To better visualize objects hierarchy in the console recently I learned the dump function, that will improve the visualization to this:

let decoder = JSONDecoder()
let house = try! decoder.decode(House.self, from: houseJson)
dump(house)

Now check the console output:

dump output in swift

 

Plot Twist – Doing the other way around

Imagine now that you want to do the inverse… You are receiving and flattened structure and you want to add some object hierarchy to it. Look at the flattened data below:

let houseJson2 = """
{
"height": 10,
"width": 20,
"color": "red",
"streetName": "Holy Street",
"houseNumber": 42
}

""".data(using: .utf8)!

And we want to transform it into this structure:

let houseJson2 = """
{
"height": 10,
"width": 20,
"color": "red",
"address" : {
"streetName": "Holy Street",
"houseNumber": 42
}
}

""".data(using: .utf8)!

The exact inverse operation, just for educational purposes. These are only examples, you can and should adapt for your APIs data types and structure though.

Moving on next, we need to create a new struct called Address to add the hierarchy to our data.

struct Address: Decodable {
    let streetName: String
    let houseNumber: Int
}

We don’t need to add the custom coding keys to Address because the decodable protocol already knows how to parse this object, thank Swift for this. And finally, now we can build the new House custom decodable structure:

struct House2: Decodable {
    let height: Int
    let width: Int
    let color: String
    let address: Address //1
    
    enum CodingKeys: String, CodingKey { // 2
        case height
        case width
        case color
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        height = try container.decode(Int.self, forKey: .height)
        width = try container.decode(Int.self, forKey: .width)
        color = try container.decode(String.self, forKey: .color)
        
        address = try Address(from: decoder) //3
    }
}

At the 1 mark, we are adding the Address property object to the House2 struct, here we say to the compiler that we will eventually parse this object. The 2 marks don’t include the Address key because it doesn’t exist in the second example JSON, so we have nothing to try to decode. And the last mark we use the default init from Address that he inherited from Decodable protocol to parse the object for us.

And the final result can check below:

let decoder2 = JSONDecoder()
let house2 = try! decoder.decode(House2.self, from: houseJson2)
dump(house2)

Resulting in:

final result of Handling API response hierarchy in Swift

 

Summary – Handling API response hierarchy in Swift

This is a brief intro to the powerful world of the custom decodable and how you can take benefit of it. If you want more complex codable examples you can watch this talk where are many other very interesting features to learn.

And if you are reading until here, I need to say to you thank you very much and I’m glad this helps, especially me, understand the coding key decodable mechanics. Any comments or suggestions please share them below.

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

credits: image