I recently worked on a project for a client that had a lot of data coming from a web service as JSON. The data was comprised of associative arrays (or dictionaries as they are known in other languages) with each key pointing to a set of data. Each of those sets of data can be used to instantiate one of several custom classes I created in Objective-C. Using TouchJSON from the handy TouchCode library I was able to translate the incoming JSON string to an NSDictionary object. An NSDictionary of data is much nicer than a raw string of JSON, but what I really wanted was to take each data set in the JSON and use it to initialize the custom classes I created.
My first pass at this was overly repetitive. I wrote a bunch of methods on my ResponseHandler class (which, as the name suggests, handles responses from the web service) that would consume each entry in the dictionary and return an instantiated object. So I had a bunch of methods on the ResponseHandler that looked like “newJSON”. Kind of messy if you ask me and I was not keeping in line with the DRY principle.
I did some digging into Objective-C and found that it is possible to do some nifty things with the language. One of them being dynamic class creation. With this new bit of knowledge, I had a clean way to resolve my lack of DRYness (…and incidentally, made me happy since I come from a Python background where dynamic class creation is relatively normal).
Lets take a contrived example of a web service which returns data about various shapes to the client. The JSON data retrieved from the web service can be used to instantiate two shape classes: Square and Circle.
If we make a request to this web service we get back the following JSON:
{'SquareObject': {'width': 10, 'height':10, 'center':[5,5]}, 'CircleObject': {'radius':10, 'center':[2,2]}}
Again, with TouchCode’s TouchJSON class, the JSON string above can be turned into cocoa objects.
The shape classes have the following interfaces:
@interface Square : NSObject {
NSNumber *width;
NSNumber *height;
CGPoint center;
}
- (id)initWithJSON:(NSDictionary *)jsonData;
@end
@interface Circle : NSObject {
NSNumber *radius;
CGPoint center;
}
- (id)initWithJSON:(NSDictionary *)jsonData;
@end
Notice I have a method on each class that has the same signature:
- (id)initWithJSON:(NSObject *)jsonData;
This method will be used to instantiate each class with the appropriate data.
The final setup requirement is to create a NSDictionary that maps the JSON keys to Class objects. It lives in my ResponseHandler class where I also instantiate the shape objects, but you can do this where ever makes sense for you.
NSDictionary *classMap = [NSDictionary dictionaryWithObjectsAndKeys:
[Square class], @"SquareObject", [Circle class], @"CircleObject", nil];
In my ResponseHandler class, where I am instantiating the shapes from JSON, I iterate through the expected keys in the NSDictionary that was created from the JSON string (jsonData) and send the initWithJSON: message to each class like so:
NSMutableArray *tmpObjs = [[NSMutableArray alloc] init];
NSObject *anObj;
for (NSString *aKey in [classMap allKeys]) {
anObj = [[[classMap objectForKey:aKey] alloc] initWithJSON:[jsonData objectForKey:aKey]];
[tmpObjs addObject:anObj];
[anObj release];
}
self.dataObjs = [NSArray arrayWithArray:tmpObjs];
That’s it. A relatively simple way to dynamically instantiate classes based on incoming data. If you want to see the complete program I used to write this program, you can find it here.