Complete Guide to Codable — Decodable

A quick and easy way to convert your JSON object to Data models

Manoj Aher
Jun 1, 2020 · 6 min read

A quick and easy way to convert your JSON object to Data models

Codable was introduced in Swift 4 which helps you convert your JSON to your model and model back to a JSON object. Codable is a typalias of Decodable and Encodable protocols. Codable is similar to Serialization in Java

In this tutorial, we will only be covering Decodable in-depth

Decodable is used for converting your JSON object to your data model with the help of JsonDecoder

Let’s take a simple example of how to decode a JSON object to your data-model

  1. Create your data model and conform to Decodable
  2. Create an instance of JsonDecoder and use decode(type: T.Type, data: Data) to decode the the JSON object to PhotoFeed.

The type passed to decode method should conform to Deocodable.

Hurray! And just by writing a few lines of code, you have your data-model ready that is mapped from the JSON object. HereJsonDecoder does the heavy lifting 🏋️‍♂️for you by mapping each key from the JSON and setting the values of the variables. The manual chores of getting the key from the JSON dictionary and then setting values in model days are far gone. All thanks to Codable

What else can Decodable do?

Optional keys in Decodable

You can mark the variables as optional for which the JSON object does not contain the value. In below example, the JSON response does not send the postKey. JsonDecoder ensures that when the variable is marked as optional, it checks the value for the key in a JSON object and then set its value if present.

// MARK: - PhotoFeed
struct PhotoFeed: Decodable {
let id, photoTnURL, photoURL, photoURLScaled: String
let curated: Bool
let postKey: String?
}

CodingKey

CodingKey tells the JSONDecoder to map the keys present in the container with the assigned variables. The above API response has id and photoTnURL, which does not tell what exactly the variable represents. This can be fixed with CodingKey. So we rename the keys idfeedId and photoTnURLthumbnailUrlString

// MARK: - PhotoFeed
struct PhotoFeed: Decodable {
let feedId: String // Renamed from id
let thumbnailUrlString: String // Renamed from photoTnURL
let curated: Bool
let photoURL, photoURLScaled: String
let postKey: String

enum CodingKeys: String, CodingKey {
case feedId = "id"
case thumbnailUrlString = "photoTnURL"
case curated, photoURL, photoURLScaled, postKey
}

}

Handle Nested Data

Let’s say the API response starts sending you the location along with the feed. You can add the location in your data-model which will conform to Decodable. You can add the new Location struct in your PhotoFeed or as a new struct outside the PhotoFeed.

// MARK: - PhotoFeed
struct PhotoFeed: Decodable {
let feedId: String
let thumbnailUrlString: String
let curated: Bool
let photoURL, photoURLScaled: String
let postKey: String
let location: Location

struct Location: Decodable {
var latitude: Double
var longitude: Double

enum CodingKeys: String, CodingKey {
case latitude = "lat"
case longitude = "long"
}
}


enum CodingKeys: String, CodingKey {
case feedId = "id"
case thumbnailUrlString = "photoTnURL"
case curated, photoURL, photoURLScaled, postKey, location
}
}

Flatten out JSON nested properties

JSONDecoderwill help you iterate through all the container values. Use decoder.container(keyedBy:) on the decoder to get all the values present in the CodingKey. Similarly nestedContainer(keyedBy: forKey:) will allow you to iterate through the nested container. Using these methods you can iterate over all the keys in the decoder. Let's see this through an example to flatten out the structure

// MARK: - PhotoFeed
struct PhotoFeed: Decodable {
let feedId: String
let thumbnailUrlString: String
let curated: Bool
let photoURL, photoURLScaled: String
let postKey: String
let latitude: Double
let longitude: Double


enum CodingKeys: String, CodingKey {
case location
case feedId = "id"
case thumbnailUrlString = "photoTnURL"
case curated, photoURL, photoURLScaled, postKey
}

enum LocationKeys: String, CodingKey {
case latitude = "lat"
case longitude = "long"
}
// MARK: - init with decoder
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let location = try values.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
latitude = try location.decode(Double.self, forKey: .latitude)
longitude = try location.decode(Double.self, forKey: .longitude)
feedId = try values.decode(String.self, forKey: .feedId)
thumbnailUrlString = try values.decode(String.self, forKey: .thumbnailUrlString)
curated = try values.decode(Bool.self, forKey: .curated)
photoURL = try values.decode(String.self, forKey: .photoURL)
photoURLScaled = try values.decode(String.self, forKey: .photoURLScaled)
postKey = try values.decode(String.self, forKey: .postKey)

}
}

Decoding strategies

KeyDecodingStrategy:

Sometimes the JSON response not always follows the camelCase naming convention. There is a good chance that you may have a response that follows snake_case.

We can set the decoding strategy that determines how coding keys are decoded from JSON. Let’s take an example to solve this:

1.useDefaultKeys: It’s the default strategy. JsonDecoder will use the same variable names for decoding

2.convertFromSnake: It will convert the JSON keys from snake-case🐍 to camel-case🐫 keys present in your data-model

// MARK: - PhotoFeed
struct PhotoFeed: Decodable {
let feedId: String
let photoTnUrl: String
let curated: Bool
let photoUrl, photoUrlScaled: String
let postKey: String
}
let data = json.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let photoFeed = try! jsonDecoder.decode(PhotoFeed.self, from: data)

3.custom: You can have your custom implementation which returns the correspondingCodingKey present in your data-model. The below data-model has keys which have youtube prefixed before every key present in JSON. Using custom decoding strategy, we can remove the prefixed stringyoutube from every JSON object key so it maps to the variable in PhotoFeed.

Decoding Dates

JsonDecoder supports decoding dates from the JSON response. Here is a list of supported date decoding strategies.

1.deferredToDate: Number of seconds elapsed since 1st Jan 2001 represented in double. A very rare chance you would be using this.

2. iso8601: This is the most widely used date-format. Use this link to understand more about date-format used under iso8601

3. formatted(DateFormatter): You can add a custom DateFormatter with required dateFormat

4.custom((Decoder) -> Date): You can write custom code for parsing your date. Let’s say you have a bad JSON containing two date formats, here is a quick way to resolve it using .custom decoding strategy

5.millisecondsSince1970: It decodes date in milliseconds since midnight UTC on January 1st, 1970

6.secondsSince1970: Decodes date in seconds since midnight UTC on January 1st, 1970

Decoding Raw Data

JsonDecoder supports strategies for decoding your raw data. Here is a list of supported data decoding strategies

1.base64 : decodes data using Base 64 decoding

2.custom((Decoder) -> Data) : decodes data using a custom function implemented by you.

By now you already got a gist of how the default custom decoding works and how would you use them. Still find it difficult to understand, comment below and I will add gist for you

Decoding Exceptional Numbers

This strategy is used by a JsonDecoder when it encounters exceptional floating-point values. In your lifetime you must have encountered when your server returns an invalid “NaN”. Similarly how to handle +ve infinity and -ve infinity values (which you might have never encountered), if not handled this would just CRASH 💥 your app.

Here is how you handle nan, +veInfinity and -veInfinity

That’s pretty much you what you get in Decodable 🙌

Did I miss a use-case? Let me know in the comments below!

Archived — Flawless iOS

Archive of Flawless iOS publication — no longer accepting submissions

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store