Swift 4.0 Codable — Decoding subclasses, inherited classes, heterogeneous arrays
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.
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:
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.
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.
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.
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 —
Line 20: Get the array of drinks —
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
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!
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
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.