Swift 4 Decodable: Beyond The Basics 📦

One of the features that I was looking forward to this year WWDC was Codable, which is just a type alias of the Encodable and Decodable protocols.

I’ve just spent a whole week shifting my Swift projects from using custom JSON parsers to Decodable(while removing a lot of code! 🎉), this post showcases what I’ve learned along the way.

Keppel Container Terminal in Singapore by William Cho

Basics

If you haven’t seen it already, I suggest you to watch the related WWDC session (the Codablepart starts past 23 minutes).

In short: you can now convert a set of data from a JSON Object or Property List to an equivalent StructorClass, basically without writing a single line of code.

Here’s an example (it’s a Swift Playground!):

What the Compiler Does Without Us Noticing

If we look at the Decodable documentation, we see that the protocol requires the implementation of a init(from: Decoder) method.

We didn’t implemented it in the Playground: why did it work?

It’s for the same reason why we don’t have to implement a Swifter initializer, but we can still go ahead and initialize our struct: the Swift compiler provides one for us! 🙌

Containers by Finbarr Fallon

Conforming to Decodable

All of the above is great and just works™ as long as all we need to parse is a subsets of primitives (strings, numbers, bools, etc) or other structures that conform to the Decodable protocol.

But what about parsing more “complex structures”?
Well, in this case we have to do some work.

Containers by Jumilla

Implementing init(from: Decoder)

⚠️ This part might be a bit trickier to understand: everything will be clear with the examples below!

Before diving into our own implementation of this initializer, let’s take a look at the main players:

The Decoder

As the name implies, the Decoder transforms something into something else: in our case this means moving from a native format (e.g. JSON) into an in-memory representation.

We will focus on two of the Decoder’s functions:

  1. container<Key>(keyedBy: Key.Type)
  2. singleValueContainer()

In both cases, the Decoder returns a (Data) Container.

Now you know what’s up with all these container pictures! 😜

With the first function, the Decoder returns a keyed container, KeyedDecodingContainer: to reach the actual data, we must first tell the container which keys to look for (more on this later!).

The second function tells the decoder that there’s no key: the returned container, SingleValueDecodingContainer, is actually the data that we want!

The Containers

Thanks to our Decoder we’ve moved from a raw native format to a structure that we can play with (our containers). Time to extract our data! Let’s take a look at the two containers that we’ve just discovered:

KeyedDecodingContainer
In this case we know that our container is keyed, you can think of this container as a dictionary [Key: Any].

Different keys can held different types of data: which is why the container offers several decode(Type:forKey:) methods.

This method is where the magic happens: by calling it, the container returns us our data’s value of the given type for the given key (examples below!).

Most importantly, the container offers the generic method
decode<T>(T.Type, forKey: K) throws -> T where T: Decodable
which means that any type, as long as it conforms to Decodable, can be used with this function! 🎉🎉

SingleValueDecodingContainer
Everything works as above, just without any keys.

Catene de Containers by BD-76

Implementing our init(from: Decoder)

We’ve seen all the players that will help us go from data stored in our disk to data that we can use in our App: let’s put them all together!

Take the playground at the start of the article for example: instead of letting the compiler doing it for us, let’s implement our own init(from: Decoder).

Step 1: Choosing The Right Decoder

The example’s data is a JSON object, therefore we will use the Swift Library’s JSONDecoder.

⚠️ JSON and P-list encoders and decoders are embedded in the Swift Library: you can write your own coders to support different formats!

Step 2: Determining The Right Container

In our case the data is keyed:

To reach "Federico Zanetello" we must ask for the value of key "fullName", to reach 123456 we must ask for the valued of index "id", etc.

Therefore, we must use a KeyedDecodingContainer Container (by calling the Decoder’s method container<Key>(keyedBy: Key.Type)).

But before doing so, as requested by the method, we must declare our keys: Key is actually a protocol and the easiest way to implement it is by declaring our keys as an enum of type String:

Note: you don’t have to write = “…” in each case: but for clarity’s sake I’ve chosen to write it.

Now that we have our keys set up, we can go on and create our container:

Step 3: Extracting Our Data

Finally, we must convert the container’s data into something that we can use in our app:

Step 4: Initializing our Struct/Class

We can use the default Swifter initializer:

Voila! We’ve just implemented Decodable all by ourselves! 👏🏻👏🏻
Here’s the final playground:

M.A.C.H.I.N.E. by Jon Chiang

Going further (More Playgrounds!)

Now that our Swifter struct conforms toDecodable, any other struct/class/etc that contains such data can automatically decode Swifter for free. For example:

Arrays

Dictionaries

Enums

More Complex Structs

And so on!

SANDY RICKMERS — Copenhagen by Mike Brocklebank

Before Departing

In all probability, during your first Decodable implementations, something will go wrong: maybe it’s a key mismatch, maybe it’s a type mismatch, etc.

To detect all of these errors early, I suggest you to use Swift Playgrounds with error handling as much as possible:

You can go even deeper by splitting different types of Decoding Errors.

Even on the first Xcode 9 beta, the error messages are clear and on point 💯.

CCCC Adrift by Julien Genest

Conclusions

I was really looking forward to this new Swift 4 feature and I’m very happy with its implementation. Time to drop all those custom JSON parsers!

That’s all for today! Happy Decoding!


Federico is a Bangkok-based Software Engineer with a strong passion for Swift, Minimalism, Design, and iOS Development.