Swift 4.0 Codable — Decoding subclasses, inherited classes, heterogeneous arrays

Background

Swift 4.0 has introduced a really helpful API. Codable If you have been following the Swift project and the new features in this version on the language you have probably already heard about it. I ran into some issues when working with some of our REST APIs so wanted to document them here in case other people run into the same situations.

Getting Started

If you are familar with Codable you can skip this sections, if not this is how Apple summarise is on there docs page:

Make your data types encodable and decodable for compatibility with external representations such as JSON.

There is already quite a lot of good articles out there on how to use this new API. These are the best ones, I would suggest reading them as an entry point:

Encoding and Decoding Custom Types

Ultimate Guide to JSON Parsing With Swift 4

Swift 4 Decodable: Beyond The Basics 📦

Decoding Heterogeneous Arrays

A heterogeneous array is when you have a piece of JSON which contains multiple different objects. In my case, these objects are subclasses of a base class Drink Each subclass has different attributes which need to be serialised.

Models

Firstly, the models need to be defined, conforming to Decodable protocol. Starting with the base class and then the subclass.

This is the base class and defines all the attributes associated with it. There is nothing complicated about this, so no custom code needs to be written, it just uses the standard API.

Beer has the additional attribute of alcohol_content so we need to add that. This class needs to be initialised before the base class. We need to write our own custom init method. You can see on line 11, we pass the same decoder object to the super class so that it can decode the base classes attributes.

Serialisation

This is how we convert the json into memory. Nothing complicated here, but we now need to define the Drinks object. This object contains a heterogenous array so we will need to implement the Decodable protocol ourselves.

Heterogenous Array

Line 2 shows that we add an array of Drink objects as a property to this class.

Next we create an enum DrinksKey which is used to decode the dictionary containing the array of drinks.

We also define an enum DrinkTypeKey which is used to serialise the type field for each object in the array.

And a final enum DrinkTypes with specifies all the possible drink types.

With all the enums specified we can get to the serialisation.

Line 19: Get the drinks dictionary — container

Line 20: Get the array of drinks — drinkArrayForType

Line 23: Make a copy of the drinks array — drinksArray This is needed as we be iterating the drinkArrayForType In doing so we will have no way to get the decoder object for each element in the array. See summary on this below.

Line 24: Iterate the drinkArrayForType container

Line 25: Get the current drink container

Line 26: Decode the type attribute

We can now use this decoded attribute to identify which class to decode. We have to use the copied drinksArray object for decoding however, as the iterator of drinksArrayForType has moved onto the next object. Viola!

Summary

This last step was the only way I could get the Decodable API to do my bidding. As far as I can see there is no way to get a decoder from an UnkeyedDecodingContainer or KeyedDecodingContainer This seems like a limitation of the API, but Im sure there are good reasons for it not to be possible. It does seem a little strange to expose that dependency just for this use case of decoding.

If you think otherwise, let me know, it may be worth thinking about it a little further and adding a suggestion for improvement to the Swift API itself. After all this is only the first iteration of the this API, its possible developers may have missed this use case.

Full Playground