Open Sourcing Isaac

May 29, 2014

We're proud to release the first version of Isaac, an open-source Objective-C library, which parses JSON into models. Using Isaac has vastly improved the quality of our code and decreased the crash rates for our iOS apps. It allows us to define a contract for the JSON that comes in and automatically nullifies any values which break this contract. It's been a useful tool for us, and we're looking forward to feedback from the open-source community. Check out the GitHub project page and our website.

The Problem

When we started working on a redesigned version of the LinkedIn iPhone app (which we released early 2013), we had a lot of code that looked like this:

https://gist.github.com/plivesey/c9587c314dde4e1b031e.js

The main problem with this code is that it is not type-safe. At LinkedIn, the mobile servers are released twice a day. Sometimes, a bug would be introduced and a value in the JSON would be a string instead of a number. Then, the method above would return an NSString instead of an NSNumber and potentially cause a crash a few lines later.

Attempting to fix this problem, we decided to write categories on NSDictionary and NSArray which implemented safe accessors for JSON data. Then, the code looked like this:

https://gist.github.com/plivesey/d122e45d1d35951aea93.js

This helped reduce the number of crashes but still wasn't quite what we wanted. The accessors were easy to write: the method would simply check that the key was not nil and verify that the object it was returning was the correct class. But the code was still ugly. Ugly enough that the developer who wrote this method thought it should go in a helper method instead of writing it inline. This practice also meant choosing between having hard-coded strings littered throughout our code or having a huge JSONConstants.h file.

We really wanted to use model objects. We wanted subclasses of NSObject which have type-safe properties, allowing us to access values in JSON using dot notation with autocomplete:

https://gist.github.com/plivesey/ed4c9d73a8b91949d53e.js

While the code is readable, we still needed to inflate this model from an NSDictionary. We knew it was not acceptable to write an inflation method for every model as this would be a huge waste of developer time (and would be full of dictionary accessors, so really we would just be moving the problem to a different file).

The Solution

To inflate these models automatically, we created Isaac. Isaac automatically inflates models from JSON by assuming that the property names and types will match the keys and values in the JSON. For instance, given the following JSON and models, we can create a model using just one line.

https://gist.github.com/plivesey/1668ac0789d4db18597b.js
https://gist.github.com/plivesey/9ec9feac90d784d698c8.js
https://gist.github.com/plivesey/d231f0c2fd22e0a8a4b2.js

So far, this is easy to implement and produces readable type-safe code. The implementation file for these models is also really easy to write:

https://gist.github.com/plivesey/cc610d85b111dd89fb5a.js

How it works

The library is built off of Objective-C's runtime library. This provides the functionality to inspect the properties of objects. You can enumerate the properties and query their types. The library uses this to recursively traverse the JSON and create model objects as it goes.

You may also notice that the model objects we use, declare the types of the properties as NSInteger or BOOL instead of NSNumber; there's no special logic required for this. We're just taking advantage of Objective-C's Key Value Coding. To set properties on an object, we are using setValue:forKey:, which automatically converts NSNumbers to primitives if necessary.

Other Functionality

Convert Models back into JSON

The library also provides the ability to convert model objects back into JSON. This can be useful when trying to create POST data or serializing models. It also makes debugging easier. Take a look at this snapshot taken while debugging Isaac's sample app:

po model vs po [model isc_JSONRepresentation]

This method can actually be used on any NSObject subclass, including UIKit objects. It recursively converts all the properties of the object to keys in a dictionary. This means that for most classes, you can print out most of its relevant information without needing to implement the description method.

Copy Models (or any object)

Isaac provides the ability to copy models without needing to implement NSCopying. How often have you written code which looks like this?

https://gist.github.com/plivesey/335dad1f45a4a650bede.js

This code feels like a waste of time to write and annoying to maintain. Isaac's deep copy functionality allows the developer to recursively traverse all the properties and either copy or create new objects. It also creates deep copies of arrays and dictionaries rather than shallow copies. Here's a basic look at the algorithm:

  1. If the object is an array or dictionary, run Isaac's deep-copy code.
  2. If the object adheres to NSCopying, then call copy on the object and return it.
  3. Otherwise, create a new instance of the class (using [self class]), and enumerate the properties. For each property, call isc_deepModelCopy (this is the recursion) and set the result on the new instance. If the property is a primitive, then just set the value on the new instance.

This means you can run this method on any object and create another deep copy of the object without any implementation.

Summary

Using Isaac has vastly improved the quality of our code and decreased the crash rates for our iOS apps. It allows us to define a contract for the JSON that comes in and automatically nullifies any values which break this contract. It's been a useful tool for us, and we're looking forward to feedback from the open-source community. Check out the website at http://linkedin.github.io/Isaac/.

Topics