One thing that nearly every app that communicates with external services does is parse JSON and convert it into usable model objects. I’ve seen this handled numerous ways, the most common being some sort of for loop on an NSDictionary’s keys, looking for specific strings.
1 2 3 4 5
Not only is this a major pain to read, but it makes updating models tedious and error prone. After spending years populating my models this way, I set out to find something better. In post one of this three-post series I’ll introduce the mechanisms we use to automatically marshal JSON API responses to models. In part two I’ll introduce additional automatic NSCoding and NSCopying support and additional utility methods. Part three is the ultimate open sourcing of our base model class.
Core Functionality / Requirements
Before jumping into the code I will explain some of the requirements we have for our model objects. First, assume that all of our models will inherit from a single base class that handles most of the functionality.
1) Automatic marshaling of model(s) from an NSDictionary or NSArray representation
Passing an NSDictionary or NSArray to a single method will automatically generate a complete object graph based, complete with nested models and collections of models.
2) Automatic NSCoding & NSCopying support
All models, by default, support NSCoding & NSCopying so the app can easily persist them to disk and make deep copies. Very little, if any, scaffolding code should be required to support this.
3) Automatic conversion of NSString’s to types like NSDate, NSURL, etc.
Models often times contain more complex types, like NSDates, NSURLs and even things like (UI|NS)Images. Conversion to these types needs to be easy to setup and seamless.
Now we’ll jump right into the base model class (paired down for part one) with the majority of our functionality and an example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
Woah, that’s a lot to digest. Let’s go through it and use MyModel to illustrate ESModel works.
First up is
+ (NSDictionary *)valueMap;. This method must be implemented by any sub-classes and is the basis for the conversion of NSDictionary key-value pairs to model properties. If you look at MyModel you can see it simply returns a dictionary with the keys corresponding to what would typically be JSON keys and the values corresponding to the model’s ivars.
The next method is a biggie and provides the main functionality we desire. As its name implies, fromDictionary handles converting a dictionary into a model class. Let’s go through it in chunks so it’s easier to digest.
1 2 3 4 5
These lines are rather self explanatory. Since they’re executed in the context of a sub-class of ESModel they’re initalizing an instance of MyModel and pulling out the valueMap and valueConverters. The valueConverters method provides functionality to marshall any obj-c class from some NSDictionary representation (and by extension JSON representation). More on this later.
1 2 3 4 5 6
Next start looping through the dictionary representation and attempt to pull out values based on the keys of our valueMap. So in the case of our JSON we’ll get two NSStrings, one NSDictionary (model_2) and one NSArray (array_of_model_2s). I’ve never found [NSNull null] particularly useful and prefer the behavior of nil so we skip over those values. That said, your mileage may very and adding a flag to conditionally skip nulls is easy enough.
1 2 3 4 5
Next we check for a ValueConverter for this key. A ValueConverter is a very simple block that converts one object to another. The blocks signature is:
In ESModel you’ll notice there is a method named valueConverters. This optional, overridable method returns a NSDictionary with all of the keys that should be converted from one representation to another. Looking at MyModel you can see it returns two keys: one for a single model and one for an array of models. These methods are convinentaly implemented for any model that inherits from ESModel. More on those in a second.
Finally, wrapping up fromDictionary we use KVC to set the property on our model based on the value from valueMap:
1 2 3 4 5 6 7
Next, there is a very simple method named fromArray that converts an array of dictionaries into models…as you’d expect it simply loops over the array and calls fromDictionary on itself.
The more interesting bits come after, the ValueConverter methods we touched on earlier. Let’s look at them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
The first ValueConverter method is a straight forward utility method that utilizes fromDictionary. It lets us compose complex model graphs in our JSON responses and have everything convert to the proper models. MyModel uses this to convert another embedded model in its JSON to the correct class. The second method is similar to the first, converting an array of embedded models into the correct representation.
In part two we’ll cover the following:
- Additional ValueConverter topics
- Refactoring ESModel to automatically handle NSCoding and NSCopying
Stay tuned and feel free to send any feedback or questions to email@example.com. Thanks!