Decoding Custom Types with Decodable in Swift 4
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 asString
,Int
, andDouble
- A few
Foundation
types also conform, such asDate
,Data
, andURL
- Collection and mappable types, such as
Array
,Dictionary
, andOptional
automatically gain conformance toCodable
when their contents also conform toCodable
- 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
.