A few tips about Swift’s Decodable — from starter to pro

Yi 🐍🐏
5 min readSep 8, 2022

--

Photo by Maxwell Nelson on Unsplash

This article will explain how to use Decodable` in Swift` with a few use cases.

Before we get started, a good tool I want to introduce to make your JSON result looks pretty is JSONFormatter.

Section A: General Base Case

Given this url, which will return a JSON file contains a list of dog image urls. Example could be looked like:

{
"message": [
"https://images.dog.ceo/breeds/hound-afghan/n02088094_4352.jpg",
"https://images.dog.ceo/breeds/hound-afghan/n02088094_5559.jpg",
"https://images.dog.ceo/breeds/hound-afghan/n02088094_7894.jpg"
],
"status": "success"
}

It’s easy to see there are two keys in this structure, message and status. Then, our Swift code could be looked like:

struct BreedResponse: Decodable {
let message: [String]
let status: String
}
let result = try? JSONDecoder().decode(BreedResponse.self, from: data)

In the code above, a JSONDecoder instance here will try to decode the Data instance received.

Section B: General Case with Custom Key

In some scenarios, the JSON key’s name does not do a perfect job representing the meaning of the content/value, so, to make your code more readable, developer would possible write a meaning name, which could be long and different from the JSON key name. In this case, developer need to associate your model’s property with the JSON key name.

So, our model could be modified with better named properties.

struct BreedResponse: Decodable {
let imageURLList: [String]
let status: String

enum CodingKeys: String, CodingKey {
case imageURLList = "message"
case status
}
}

In the code above, if we need to map a name to another, we need to specify the name in JSON structure. Otherwise, just list it as another case in enum.

Now, the model’s property has more meaningful name, which indicates the what exactly result we are expecting here. This will improve the readability for you and other coworkers.

Section C: Nested Structure

In the real world case, a well structured JSON result could provide enough information the app is trying to request, while explain the relationship structure of the models as well. JSON result could have nested structures to present abundant information, so the app would not make multiple URL request to reach the final result. So, the developer would expect a nested JSON result in any time.

A good example of JSON result:

{
"reference": "John 3:16",
"verses": [
{
"book_id": "JHN",
"book_name": "John",
"chapter": 3,
"verse": 16,
"text": "\nFor God so loved the world, that he gave his one and only Son, that whoever believes in him should not perish, but have eternal life.\n\n"
}
],
"text": "\nFor God so loved the world, that he gave his one and only Son, that whoever believes in him should not perish, but have eternal life.\n\n",
"translation_id": "web",
"translation_name": "World English Bible",
"translation_note": "Public Domain"
}

The way we decode sample result like this would be:

struct VerseInfo: Decodable {
let bookID: String
let bookName: String
let chapter: Int
let verse: Int
let content: String

enum CodingKeys: String, CodingKey {
case bookID = "book_id"
case bookName = "book_name"
case chapter
case verse
case content = "text"
}
}
struct BibleContent: Decodable {
let reference: String
let verses: [VerseInfo]
let content: String
enum CodingKeys: String, CodingKey {
case reference
case verses
case content = "text"
}
}
let result = try? JSONDecoder().decode(BibleContent.self, from: data)

Something interesting here deserves to be mentioned:

  1. VerseInfo needs to conform Decodable, because every property of BibleContent needs to conform to Decodable to make BibleContent decodable.
  2. In the model definition, we don’t need to define all the properties for every key in the JSON structure. It’s reasonably to take only important/useful values from that.

Section D: Advanced Nested Structure

Other uncommon case could be: With this url link could be looked like:

{
"message": {
"affenpinscher": [],
"australian": [
"shepherd"
],
"buhund": [
"norwegian"
],
"bulldog": [
"boston",
"english",
"french"
],
"stbernard": [],
"terrier": [
"american",
"australian",
"bedlington",
"border",
"cairn",
"dandie",
"fox",
"irish",
"kerryblue",
"lakeland",
"norfolk",
"norwich",
"patterdale",
"russell",
"scottish",
"sealyham",
"silky",
"tibetan",
"toy",
"welsh",
"westhighland",
"wheaten",
"yorkshire"
]
},
"status": "success"
}

This example is much trickier than the usual nested result, since the nested level does a non-repetitive key as the name. In this case, we need to customize how to decode the data in details.

// Model to define Breed
struct
Breed: Decodable {
let name: String
let subBreeds: [String]
}
// Model to define the JSON result.
struct AllBreedResponse: Decodable {
let breedList: [Breed]
let status: String
enum CodingKeys: String, CodingKey {
case breedList = "message"
case status
}
}
// Extension to implement the customization of JSON Parsing.
extension
AllBreedResponse {
init(from decoder: Decoder) throws {
// 1. This will be the root container.
let values = try decoder.container(keyedBy: CodingKeys.self)
// 2. This will be the common way to decode a standard swift library type.
let status = try values.decode(String.self, forKey: .status)
let allBreeds = try values.decode([String : [String]].self, forKey: .breedList)
// 3, we finish the initialization.
self.status = status
self.breedList = allBreeds.map({ key, values in
Breed(name: key, subBreeds: values)
})
}
}

In the code above,

  1. Get the root container from decoder. It could be treated as handler for the root of your JSON result.
  2. Decode data of standard Swift library type.
  3. Wrap up the initializer by assigning converted/mapped values to every properties needs to be assigned.

The tricky part of this parsing is do some manual parsing during the whole parsing process.

One more thing to mention here is: using KeyedDecodingContainer could handle more complicated nested case easily.

Conclusion

In the above, we have demonstrated how to parse the JSON result from simple case to nested case, from straight answer to the answer with customization needed.

Hopefully this article with solid example could help you understand it better.

Reference

--

--

Yi 🐍🐏
Yi 🐍🐏

Written by Yi 🐍🐏

Write the Code, Change the World.

No responses yet