Yet another guide on handling JSON in Swift
I’m aware there are hundreds of blogs/guides/libraries on how to do JSON parsing in Swift; but since the Swift team posted an official blog post (https://developer.apple.com/swift/blog/?id=37) on the subject that’s thoroughly divided opinion, I thought I’d throw in my two cents as well.
I’m not a fan of introducing a third party dependency in my code just to save myself a few lines of code. Writing the extra lines makes it easier to debug and adds clarity to what the code is doing to someone else reading it who might be unfamiliar with the library in question.
I investigated the popular libraries out there and didn’t really like any of them. They all seemed over-engineered and unnecessary (I don’t mean to disparage their authors. Horses for courses). Not to mention the added bloat which increases the compile time and the size of the binary.
SwiftyJSON (https://github.com/SwiftyJSON/SwiftyJSON) seems to be the most popular option out there but I didn’t really like the API. It looks like a solution designed to extract data from JSON in an ad-hoc way. I prefer backing every JSON object with concrete objects like structs or classes to provide “code-as-documentation”. Also SwiftyJSON has some performance issues (http://stackoverflow.com/questions/29252158/swiftyjson-performance-issues) and also seems to have been abandoned by the maintainers.
ObjectMapper (https://github.com/Hearst-DD/ObjectMapper) is another popular one. I didn’t particularly like this either as it doesn’t save THAT many lines of code and adds too much magic behind the scenes in saving the few that it does. I also don’t like custom operators as used in this library because it’s never obvious what they are doing. In the end I decided that native JSON parsing was the most straighforward and simple way to proceed.
This is the JSON I will be using in all examples in this post:
It is a response from the Trakt.tv API (http://docs.trakt.apiary.io). The response contains a lot of information but I am only interested in the name, year of release, overview and image to create a model object.
The first method of parsing I devised was very similar to what the Swift team put forward in their blog post. I defined a “JSONObject” protocol to document JSON objects and a “JSONError” enum as below:
The native “JSONSerialization” class can be used to parse JSON data into a dictionary like this:
The resulting dictionary can be extracted into a “Movie” struct based on the above JSON example using “guard”s and “if let”s as below:
While that’s pretty verbose, it’s blatantly clear what each type should be and what the code is doing. The complexity is all abstracted away in the model so the caller will only instantiate the object with the serialised dictionary from “JSONSerialisation” without creating clutter. Debugging this code was a breeze and it serves as excellent “code as documentation” as well.
After the official Swift blog post, this Twitter conversation (https://twitter.com/nicklockwood/status/775457511579279361) got me thinking that I could optimise my initialiser a bit and remove the repetitive type checks and also provide a way of querying nested objects.
The solution I came up with was this:
A “JSONDictionary” object is created from the response data which has a property that just holds the dictionary. The “valueFor(jsonKeyPath:)” recursively traverses a “/” separated key path to return the required value making retrieving nested objects very easy.
Going back to the earlier “Movie” example; the “JSONObject” protocol is now amended to:
The “Movie” struct is now much more concise as follows:
I know I’ve cheated a bit by putting the do/catch blocks on one line, but since the lines aren’t that long, I think it’s worth compressing it a bit.
Putting everything together, JSON data can be parsed like this:
If your top level JSON object is an Array and not a Dictionary, a “JSONArray” struct can be used to parse the string into an Array of “JSONDictionary”s as shown below:
Serialising Swift objects into JSON is a topic that actually hasn’t been covered anywhere near as widely as JSON parsing. I experimented with reflection for this so any object would get JSON serialisation for free by conforming to a protocol but each use case I thought of called for ever so slightly different requirements. For example, there’s no good way of dealing with Optionals in a reflected “Mirror” object. Also if the JSON keys need to be slightly different from the variable names, Reflection fails again.
For me, the best way to achieve this in a “generic” way would be if Swift had support for custom attributes, like custom annotations in Java. Each property could have a custom attribute specifier that contains information on how to serialise it, then via a protocol extension, they could be serialised into JSON. Unfortunately that feature does not exist yet so I’ve come up with a fairly simple but non-generic way of serialising objects into JSON.
A “JSONSerializable” protocol defines the behaviour for any object that can be serialised into JSON. It has an extension that serialises a Dictionary into JSON data; and the work of creating the dictionary representation of an object is left up to the object itself for simplicity and ease of customisation.
The “Movie” struct from earlier can now be extended to allow for serialisation as well like this:
Implementing the “dictionaryRepresentation()” method for each object is a bit of a faff, but it’s definitely worth it for clarity and customisation.
Obviously there’s no one correct way of doing JSON parsing and serialisation. This is my favoured method as it’s great for “code as documentation”, avoids a needless third party dependency and is easily customisable for each app’s specific use case. Let me know your thoughts in the comments!