Decoding Custom Types with Decodable in Swift 4

Paris Xavier Pinkney
4 min readJan 28, 2019

--

Photo by Steve Johnson on Unsplash

This blog post can also be read on my portfolio site at PXPGraphics.com.

Conforming To Codable

There are two ways of implementing Codable on your own types—automatic and manual declaration. Both of which are essential in understanding to proper serialize data to and/or from any Swift type. First, we’ll go over the fundamentals for Codable conformance:

  • Many standard library types conform to Codable by default, such as String, Int, and Double
  • A few Foundation types also conform, such as Date, Data, and URL
  • Collection and mappable types, such as Array, Dictionary, and Optional automatically gain conformance to Codable when their contents also conform to Codable
  • When a custom type consists of properties which all conform to Codable, then the custom type automatically conforms as well with proper declaration
  • Custom Codable types may be defined as either structs or classes

Decoding Custom Types Automatically

Next, we’re going to use The Movie Database API for this series, because it will provide the best real-world example for using Codable in your own application(s). For instance, let’s start with a JSON object representing a movie genre—say, Action:

{
"id": 28,
"name": "Action"
}

A genre model consists of an id and name. Here’s an example of this model in Swift:

struct Genre {
let id: Int
let name: String
}

As we said above, because Int and String already conform to Codable, by having Genre also conform, we get an automatic conformance that satisfies all requirements of Encodable and Decodable:

struct Genre: Codable {
let id: Int
let name: String
// The Codable methods init(from:) and encode(to:) are implemented by default.
}

The next example is for a larger JSON object representing the movie, Aquaman. Let’s assume this movie object, for the time being, is a simplified data model — only representing the id, title, tagline, and genres.

struct Movie: Decodable {
// Int and String both conform to Codable.
let id: Int
let title: String
let tagline: String
// The Decodable method init(from:) is implemented by default.
// Movie is still decodable after adding an Array of Genre, which is also decodable.
let genres: [Genres]
}

Coding Keys

In order to support manual decoding or encoding, Codable needs to know which data attributes should be transformed into Codable types, such as Int, Float, Double, Bool, String, etc.

Codable types can declare a nested enumeration called CodingKeys which conforms to the CodingKey protocol. When this enumeration is declared, the cases listed represent the properties which should be encoded or decoded from the external data to your codable type.

Properties can be ignored from encoding or decoding by omitting their key from the enumeration declaration.

Note: When omitting properties, a default value must be provided in the initializer to support automatic Codable conformance.

Because most languages have their own naming conventions, we can’t assume that a given key from JSON should be the exact property name in its Swift representation. Thankfully, CodingKey also supports RawRepresentable which allows our properties to follow the Swift API Design Guidelines, while the raw value can represent the JSON key.

The example below uses alternative keys for our Movie model.

struct Movie: Decodable {
// Int, String, Date, Double all conform to Codable.
let id: Int
let title: String
let tagline: String
let releaseDate: Date
let voteAverage: Double
let voteCount: Int
// Some JSON keys should be renamed to follow the API Design Guidelines.
enum CodingKeys: String, CodingKey {
case id
case title = "original_title"
case tagline
case releaseDate = "release_date"
case voteAverage = "vote_average"
case voteCount = "vote_count"
}
}

Decoding Custom Types Manually

Even though codable types handle both encoding and decoding, we should be explicit about the functionality we would like to expose when implementing the protocol(s) on our own types. Therefore, if our custom type will only be used to decode values, we should conform to Decodable instead of Codable; we should conform to Encodable when we only need to encode values as well.

In the example below, the Movie structure is extended to conform to the Decodable protocol by implementing its required initializer, init(from:):

extension Movie: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int.self, forKey: .id)
title = try values.decode(String.self, forKey: .title)
tagline = try values.decode(String.self, forKey: .tagline)
// We decode the value to String, then transform into the desired Date type.
let releaseDateString = try values.decode(String.self, forKey: .releaseDate)
releaseDate = DateFormatter.date(from: releaseDateString)
voteAverage = try values.decode(Double.self, forKey: .voteAverage)
voteCount = try values.decode(Int.self, forKey: .voteCount)
}
}

Note: For more information about the container types used when customizing the encoding and decoding process, see KeyedDecodingContainerProtocol and UnkeyedDecodingContainer.

--

--

Paris Xavier Pinkney

Twitter: @pxpgraphics. Senior iOS Engineer + Designer (@udemy, @scribd, @vlnrable, @marqeta). Son + Brother. OAK born. LAS raised. FLG educated. CALI native.