ABOUT
TEAM
WORK
CONTACT
|
BLOG

GirAppe blog

2016/05/24 - go to articles list

JSONMappable - Simplify JSON mapping into objects with SwiftyJSON

One of the most common tasks of iOS apps is getting and parsing data acquired from some backend service. And, honestly, sometimes it is a real pain in the... Windows Vista, to do it both easy and right. In the majority of projects I was using more or less REST/RESTful services. What they all had in common was the JSON data format.

There are many ways to handle it and when you write same things over and over again, you start wondering, how to do it more generically, saving up some time for a cup of coffee. It is not rocket science - most probably you have already come up with similar ideas. However, it seems nice to sum this up from an almost 3 year perspective.

First step - get the data

In most cases, preparing the data is some kind of backend service responsibility. Still, for the sake of the test, there are some tools you can use to prepare/mock the sample data.

Note: There are several nice tools that you can use for tests. I can recommend:

Second step - Handling JSON

In most cases, you'll receive some NSData object from the backend. In, let's name it "system way", you'll want some sort of dictionary out of it. This is achieved by using NSJSONSerialization method:

Swift:

class func JSONObjectWithData(_ data: NSData,
                         options opt: NSJSONReadingOptions) throws -> AnyObject

Then, you'll usually cast it to some kind of dictionary type. Also, as it throws, you need to handle the error. In most cases, as JSON values could be different (strings, ints, nested dictionaries, arrays etc), it will be casted into [String : AnyObject]. Although, with optionals, parsing this kind of dictionary is not that bad, it still is not very convenient either. And when it comes to nested dictionaries and arrays, it gets even worse - a lot of casting and handling, loads of boilerplate code, not really readable. Consider this "pyramid of doom" below:

if let dict = NSJSONSerialization. ... {
  if let user = dict["user"] as? [String: AnyObject] {
    if let profile = user["profile"] as? [String: AnyObject] {
      if let skillsArray = profile["skills"] as? [String] {
        // Utilise skillsArray
        // ...
      }
    }
  }
}

Note: Of course we might try to fit that in one line, but that will be even more messy.

Solution - SwiftyJSON

The common solution is to use some kind of library dedicated to JSON handling. My personal best is SwiftyJSON. Instead of dealing with dictionary, with all its flaws, we create a JSON object.

let json = JSON(data: dataFromNetworking)

SwiftyJSON has some nice features. First of all, it allows subscript, which always returns JSON instance. To get actual values, you just use JSON properties, like ".string" or ".int" (they returns optionals), or their "siblings" ".stringValue", ".intValue" (returning unwrapped value, or defaults if not existing).

if let skills = json["profile"]["skills"].array { // Returns optional
  // ... skills is Array<JSON>
}
// OR
for skill in json["profile"]["firstName"].arrayValue { // Returns value or [] if no field
  // ... skill is JSON
}

This is very convenient, especially when accessing optional fields, which are not necessarily present in json. SwiftyJSON github contains a lot of good examples - and I can't imagine working with JSON's without it anymore.

JSONMappable Protocol

SwiftyJSON itself provides quite nice way of interacting with JSON data. The most common use case, when handling JSON data will look like following: First step is to acquire networking data, then create JSON object, and finally, parse it into one or more Model objects.

Graph 1

In order to keep it simple and logically divided into components, I usually place parsing logic into the model class (either into main class definition, or into some extension/category).

Hence I propose JSONMappable protocol:

  /**
   *  JSON Mappable
   */
  protocol JSONMappable {
    init?(json: JSON)
  }

Every class adopting JSONMappable should implement a failable initializer, which takes JSON object as a parameter. You might ask - "Ok, is that all? Just make an initializer with JSON? Why do we need the protocol then?"

Well - the core concept was to create generic and reusable code to handle JSON data. You can then adjust your networking code, to look like:

func getObjectOfType<T:JSONMappable>(type: T.Type, ... , success: ((T)->())? = nil) {
  // networking stuff, that
  // results in data of NSData
  // ...
  if let
    json = JSON.json(data),
    mappedObject = T(json: json)
  {
    // handle mapped object
    success?(mappedObject)
  }
  else {
    // handle serialization error
  }
}

This allows to write "Network data to Model" part once, in a general way, and use it throughout the projects. Then, for a specific model, just fill required parsing method.

To sum up, JSONMappable protocol is a very simple concept, and most probably you are already using something similar. Anyway, it is a good pattern, that allows to keep the networking code clean, generic and logically separated.

In the next post, I'll try show how to create generic wrapper for Networking, using Promises and JSONMappable.

Andrzej Michnia
Software Developer