Consuming JSON in Swift (hmm 3) sans Cocoapods Part 1

Rajiv Jhoomuck
4 min readMar 13, 2017

After the introduction of Swift, many people have looked into how to consume JSON from web services and the reason for that is mainly because of Swift strong type system. Many third party frameworks/libraries germinated too:

All of these frameworks/libraries have their merits and each of them use the coolest features that the language has to offer. It is definitely enriching for every developer to see how all the libraries are implemented and what Apple has to say on the subject. Then comes the question about whether to use them in your projects. Bringing dependencies into your projects is a risk that you have to prepare yourself against. In one of my projects, I had used one of the above library and then Swift 2 came out. You can guess the rest of the story…

To cope with the problem, I started by making modifications in the library rather than wait for a fix from the author. But then I asked myself: What happens when Swift (2+n) comes out? It turns out that decoding JSON is not that hard and does not require unorthodox operators.

So let’s try to confront the problem by using simple patterns that mere mortals (developers) can understand. For this exercise, let us assume that we have a web service that returns a simple JSON stream as the following:

Example JSON response

We get back a list of persons at the root level and each person has a name (obviously), an age and an address.

We will keep 2 models in our app to represent this information:

Base models for our exercise

Here are some rules that we have set for this exercise:

  • To be able to create an address, we need the street number, the street name and the city.
  • To be able to instantiate a person, we need the name but if the age is not specified, we can set it to zero. That is the how the web service represents a new born. As for the address, it is optional.
  • When parsing the list of persons, if some required fields are not present for any object (JSON), we can skip and return nil for that object. (In a future post, I will come back to do some error reporting, which is of course very important.)

Crusty and friends taught us some very interesting techniques to make our code less error prone, so let us use them.

We will start with a protocol that our models will conform to.

JSON Initializable Protocol

Types conforming to this protocol will define an enum that will store the strings (keys in the JSON) and a failable initializer (in case some requirements are not met). Regarding the enums, we will let Swift implicitly assign the raw values (strings) which allows us to completely get rid of magic strings polluting our code. Let us see what this gives for the Address type:

Address Take 1

As promised,

  • we do not have literal strings sprinkled in our code and
  • if some required fields are absent fail the initialization routine.

Note: We are putting the failable initializer in an extension so that we do not lose the default memberwise initializer for the types (Address, Person).

While this is nice, we can do better. We observe a pattern here: let streetNumber = json[Key.streetNumber.rawValue] as? Int. Let us factor that out into a method. But where do we put that routine? We cannot put that in each of our models, therefore the obvious place to put it is in the JSONInitializable protocol:

Extension on JSONInitializable

Here we have a static method on all conforming types. This method is generic over a type T, which means that we only need to pass in the jsonObject and the required jsonKey. If there is a value for this key that can be coerced into the expected type, we return it else we just return nil.

This takes care of issues in JSON files where either we have a null or the key-value pair is not present. Remember, we have decided to be lenient with what we get as JSON.

Now our models can be re-implemented as follows:

Complete Models

Conclusion

Using this technique, our code is succint and maintainance is a minor issue. There is a clear separation of responsibility. Instead of constructing our models in our network manager/client, we just convert the JSON data into Foundation object and let each type proceed with its initialization.

The fact that we have encapsulated all this functionality in a protocol, serves as documentation. Also we have added conformance on an extension to clearly document that a Person can exist on its own and the JSONInitializable conformance is an extrinsic capability; that allows a client to instantiate a Person with some JSON.

Using an enum to encapsulate JSON keys also gives many advantages over plain strings:

  • security that during extraction, we will not pass in any arbitrary string: Person.value(for: “foo”, in: json)
  • readability: Person.value(for: .name, in: json) has more meaning than json[“name”] as? String

Finally our network manager’s job is to make a request to our web service and return the data to us. That’s it! Our models and our network managers become more testable with this approach. Typically the code in our network managers might look like this:

example Network Manager Code

While this is not a complete solution for every project, it will fit in many. Keep in mind, all of the JSONInitializable protocol fits in less than 15 lines:

JSONInitializable Protocol (complete)

In a future post, we might look into how we can enhance this technique and make it fit other use cases.

I would like to thank these amazing developers who were kind enough to review this article:

--

--