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.
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:
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
NSArray which implemented safe accessors for JSON data. Then, the code looked like this:
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:
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).
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.
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:
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.
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:
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
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?
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:
- If the object is an array or dictionary, run Isaac's deep-copy code.
- If the object adheres to NSCopying, then call copy on the object and return it.
- 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.
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/.