Swift 4 Codable in Real life

      No Comments on Swift 4 Codable in Real life

I was one of those people screaming to the miracle in the WWDC room when they first presented Codable in swift 4. I could not wait to put my hands on this new swift feature and starting using it.I was one of those people screaming to the miracle in the WWDC room when they first presented Codable in swift 4. I could not wait to put my hands on this new swift feature and starting using it.

Personally I believe that this swift 4 feature is awesome, but when it comes to real life it’s not always applicable. If for example you are trying to map an entity from different sources you might need to create two different objects to fulfil the way the entity is described in each source (song from Spotify, song from iTunes, song from wherever). Another common scenario is that the same object is returned as a json with different keys depending on the api that returns it. While this might indicate a certain level of unhealthiness of the backend you are talking to, it is still a problem I saw multiple times that might affect our choices in our app architecture, expecially if it is one of those problems that you must live with and build on top of it (if it never happened to you you are very lucky).

In this article we will talk about this scenario: Codable for the same object, different keys from different json responses.

So far in swift there was no real direct conversion from json to structured data, but Codable promises to allow a native conversion, and not just from json encoded objects, but potentially from (and to) any encoded format.

Before Codable…

Let’s suppose we have a service that returns students in a school. The json for a single student would be something like

So let’s create our entity in app.

With previous version of swift to decode our json into a Student we had to convert the json data into a dictionary and then parse it manually. The code was resulting something like this:

This code might change based on your app architecture and on the fact that you might be using some helper library, but it won’t be much different.

If something goes wrong in the decoding, what happens is that your return value is nil with no further informations around what actually went wrong in the decoding.

If you want a better error handling, than your init should become throwable, the decoding should be done in a separate guard statement for each key and each else should throw an error for missing key, unexpected value or type mismatch.

… After Codable

Codable saves you from all of that. You can get for free the initialiser to decode the object, the error explaining what went wrong and potentially you can get serialisation/deserialization from different data sources (other than dictionaries/json). How?

Yes… it is simple as that.

Focusing on the Decodable part of the Codable feature, making your class conforming to Codable  (or just Decodable ) will give you for free a basic initialiser so that in you networking layer, when you receive your jsonData you will be able to do something like that:

Pretty cool right? But where this magic is coming from? Did you notice that our variableNames has exactly the same name as the field we are mapping in the json? Well that’s the key.

Decodable explained

The compiler in compile time creates for us an initialiser for the Encodable&Decodable protocols (aka Codable) and uses for keyed archivers the var names as keys and their types to map the object. But life it’s not perfect and in swift programming we usually use camel case for var names so that last_name would be in our apps most likely lastName. But what happens when we change the var name?

You will get an error of course.

keyNotFound(__lldb_expr_45.Student.(CodingKeys in _8FCE288CA3061383EB8EA2B77A9AC5E6).lastName, Swift.DecodingError.Context(codingPath: [], debugDescription: “No value associated with key lastName (\”lastName\”).”, underlyingError: nil))

A very readable one. Here we understand immediately that there is a key that was not found. Specifically “No value associated with key lastName (\”lastName\”).” so, the default implementation of Decodable (in this case) is trying to map the var lastName with a field that in the json does not exist. Apple gives us a way to deal with this kind of var name mismatch.

We need to add an enum called CodingKeys  in our struct to define an explicit custom mapping. This CodingKeys  enum is implicitly created by the compiler in compile time, but of course the default key will be the variable name, and that’s the reason why without this specification for a custom key we were having an error in our previous code sample. Running our example this time will give us a valid Student  instance.

Also the initialiser is not voodoo magic. It is implicitly created by the compiler in compile time, but it looks something like this:

So, first we tell to the decoder which keys will be used to get a key container, then using this key container we can decode each var using the associated key and its value type.

The generated initialiser will always have CodingKeys.self  as CodingKey, and that’s what makes the nested structure with name CodingKeys  so special. Creating a custom initialiser you are of course free to change this fact and use a different CodingKey compliant data structure, but in this case you will have to write all the decode for all of your variables.

Internally each decode(_ forKey:)  method will look like this

Obviously the code in this snippet is not the real implementation of the method but it should just give you a general idea about what actually really happens for each one of those calls. In this example I chose the Bool decoder method, but there is a decode method for each decodable Type, including a generic one where T is Decodable  (so you can have a Decodable class containing a var that is itself Decodable).

The initialiser for decoder can be explicitly created if you need some further customisation on object creation, but in general you will not really need to write it explicitly because swift compiler does it for you.

So far I think I wrote here nothing you cannot find elsewhere. You are probably thinking “cool, I already know all this stuff“. That’s right, but this is not the scope of this post. The title is “Codable in Real life“, So what can actually happen in real life?

The problem

What happens if https://myService.com/students?id=12345  returns

But https://myService.com/class?id=1234 returns

Well that’s trouble, because I would like to have my SchoolClass object to be something like this:

Which is totally legit, because Codable is able to deal with nested Codable types.

The solution

So here what I want is to be able to decode my student for both services, but the two services returns different keys for the lastName (or last_name) field.

Apple doesn’t help us with that. Following the normal Codable way we must create a different Student entity for each one of our non conformant services. Well that sucks…

But we can do something about that.

For instance it’s my personal opinion that the decodable entity should not know the keys used in the backend for the mapping. This should be responsibility of the closest object to the backend which is in our networking layer. If there must be an object responsible of knowing the mapping keys, that must be there.

Can we inject in our decodable object the keys we want to use, based on the service that we are querying?

YES WE CAN!

First let’s take a look at the enum nested in our Student class. It is a String type enum that conforms to a protocol: CodingKey. We can then define a protocol for StudentKeys inheriting from CodingKey.

Why all static, and why do they all are of type Self?

Do you remember the init(from decoder: Decoder)  we explicitly wrote before?

When you define the container you ask to the decoder for a container object using a specific set of keys. The method container.decode(Int.self, forKey: .id) in the parameter “forKey” wants an object of the type <CodingKey> used before to obtain the container.

We protocolized the keys that we are expecting in our Student struct. Now what we have to do is to make injectable the keys in our entity so that each api requester can inject keys according to their own responses.

To do that we must change a little our Student struct:

Our Student  became a generic type that accepts Keys as generic parameter. These keys must be conforming StudentKeys .

Our CodingKeys  also changed. This enum is no longer a String enum type but to remain conformant to CodingKey it must have the variables stringValue  and intValue .

We can write them so that stringValue  maps CodingKeys from the Keys  generic type. The swift compiler will still create the initialiser for us and since the default implementation creates a container using the nested CodingKeys  enum, we are all set to use our Student  injecting our keys.

Also our SchoolClass  has to change a little bit to accept the generic parameter for StudentsKeys .

How a StudentKeys implementation should look like? For this particular example I chosed a struct.

Now we have our two studentKeys. One to decode the https://myService.com/students?id=12345 and the other to decode the https://myService.com/class?id=1234

 

2+

Leave a Reply