Swift — Parse JSON like a boss

Published Oct 31, 2017

If you have an app, I can bet that it talks to the network. And if it does, chances are you’re using JSON. In this article we are going to see a straightforward approach that works and requires very little code.

Here is the link to the corresponding online Playground

Requirements

As always, its useful to start with requirements instead of rushing into the code.

What do we want?

  • Simple, Lightweight, Extensible, Easy to use
  • Leave our models clean
  • Implicitely cast JSON values to the right types declared in our models
  • No crash if JSON key is not there, nor returns nil, it simply shouldn’t do anything

Of course we do not want to add this information in our models because the models should NOT depend on the json parsing logic. The mapping should sit on top of models.

Let’s Code

Here we want to build a swift model out of a JSON representation.

{
    "id": 15678,
    "name": "John Doe",
    "stats": {
        "numberOfFollowers": 163,
        "numberOfFollowing": 10987
    }
}

With Swift, as usual, we start with a protocol.

typealias JSON = AnyObject

protocol JSONParsable {
    init(json: JSON)
}

And here is our Profile Model :

struct Profile {
    var identifier = 0
    var name = ""
}

So let’s make it conform to the protocol :

extension Profile:JSONParsable {
    init(json: JSON) {
        if let id:Int = json["id"] as? Int {
            identifier = id
        }
        if let n:String = json["name"] as? String {
            name = n
        }
    }
}

Well, that works but it’s a lot of dummy code to write isn’t it ? Plus it’s not really easy to use. If we look carefully at our code, we see that we try to cast each json value to the type of the Model property. But the Type, for example Int for the identifier is already stored in our Property!

We can do better than that, so let’s be creative!

Let’s create a custom generic operator that does just that, aka testing the json value against the type of the property. Arrow is born!

infix operator <-- {}
func <-- <T>(inout left: T, right: AnyObject?) {
    if let v: T = right as? T {
        left = v
    }
}

Now our mapping looks like this \o/

extension Profile:JSONParsable {
    init(json: JSON) {
        identifier <-- json["id"]
        name <-- json["name"]
    }
}

Much better, more concise and readable ❤️

Bonus

Supporting Optionals

But this does not work for optionals does it? Indeed, as you might have noticed, our arrow operator wasn’t working with optional properties. In order to do so, we just need to provide the optional version for it which is the following :

func <-- <T>(inout left: T?, right: AnyObject?) {
    if let v: T = right as? T {
        left = v
    }
}

That was easy wasn’t it ?

Nested Resources

And what about nested resources? For example what if my P_rofile_ model has Stats which is another Swift model_ _?

struct Profile {
    var identifier = 0
    var name = ""
    var stats = Stats()
}

Let’s use a Fat Arrow to parse nested resources!

infix operator <== {}
func <== <T:JSONParsable>(inout left:T, right: AnyObject?) {
    if let r: AnyObject = right {
        left = T.self(json:r)
    }
}

Add the mapping code for Stats :

extension Stats:JSONParsable {
    init(json: JSON) {
        numberOfFollowers <-- json["numberOfFollowers"]
        numberOfFollowing <-- json["numberOfFollowing"]
    }
}

And parsing a nested resource is now super straightforward !!!

extension Profile:JSONParsable {
    init(json: JSON) {
        identifier <-- json["id"]
        name <-- json["name"]
        stats <== json["stats"]
    }
}

Voila \o/

With this approach, we have a simple way to manage JSON to swift mapping. The parsing uses built-in swift casting capabilities to make sure JSON values are cast to the right Type. If the cast fails or the key is missing from the JSON_,_ it fails gracefully and so without setting our model property to nil or some default value. On top of that, our curstom operator keeps our mapping logic concise and readable

Icing on the cake, our M_odel_ classes (structs!👍) stay clean and adding JSON mapping is as simple as adding a new extension “Model+JSON” ❤️.

Here is the link to the online Playground, have fun 🎉🎉🎉🎉

Arrow <- -

What we have just built is actually a small library that we use for parsing JSON.

It’s available via the great Dependency manager Carthage :

github "freshos/Arrow"

Or checkout source code here: https://github.com/freshos/Arrow

Happy Coding!

Special thanks to YannickDot for proofreading

Originally published here

Discover and read more posts from Sacha Durand Saint Omer
get started