Objective-C: Working with JSON and mapping it a rich domain model

Transforming Objects to JSON and building up an object graph from a JSON response is pretty standard. But how is it done with Objective-C? Imagine a very simple class Task:

@interface Task : NSObject

@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* desc;

@end

and let’s create an instance for the class:

Task* task1 = [[Task alloc] init];
task1.desc = @"some description...";
task1.title= @"My Title";

Cool! Now imagine you want the following JSON string:

{
   "title" : "My Title",
   "desc" : "some description..."
}

Ok that should be easy, especially since there is JSON support for iOS and also built-in to the iOS 5 SDK:

NSData* encodedData = [NSJSONSerialization dataWithJSONObject:task1 options:NSJSONWritingPrettyPrinted error:nil];
NSString* jsonString = [[NSString alloc] initWithData:encodedData encoding:NSUTF8StringEncoding];

Ok, this does NOT work – the dataWithJSONObject call explodes, saying Invalid top-level type in JSON write

Trying a different JSON library does NOT fix it:

SBJsonWriter* writer = [[SBJsonWriter alloc] init];
NSString* str = [writer stringWithObject:task1];

There is no JSON string afterwards – the value is nil … That’s sad!

JSON: How does it work

Well, the above (JSON) libraries work on Foundation classes like NSDictionary. If you are able to return your object in a map form, these libs work just fine:

// a map that represents the above introduced task object...:
NSDictionary* task1 = [NSDictionary
   dictionaryWithObjectsAndKeys:@"some description...", @"desc", @"My title", @"title", nil];

NSData* encodedData = [NSJSONSerialization dataWithJSONObject:task1 options:NSJSONWritingPrettyPrinted error:nil];
NSString* jsonString [[NSString alloc] initWithData:encodedData encoding:NSUTF8StringEncoding];

Since we use the pretty write option the JSON looks like:

{
  "title" : "My title",
  "desc" : "some description..."
}

So yeah, we really need maps that are representing the object graph… Nice…

Manual maps

Having the requirement to manually implement a get object as dictionary and one create from dictionary function is a bit odd. However it is a pattern, which is used sometimes…  Something more magic/automatic would be nice, other have figured out that too…

There is a framework called JAGPropertyConverter that helps to transform object graphs into a NSDictionary that can be used with various JSON libraries. Now let’s make the object graph a little bit more complex… We add an Employee that can have n tasks…

@interface Employee : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSArray* tasks;
@end

...
...
Employee* employee = [[Employee alloc] init];
employee.name = @"Mr. X";
employee.tasks = [NSArray arrayWithObjects:task1, task2, nil];

Now using the JAGPropertyConverter, we can easily transform the graph:

...
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc] initWithOutputType:kJAGJSONOutput];
converter.classesToConvert = [NSSet setWithObjects:[Task class], [Employee class], nil];
NSDictionary *jsonDictionary = [converter convertToDictionary:employee];

...
SBJsonWriter* writer = [[SBJsonWriter alloc] init];
NSString* str = [writer stringWithObject:jsonDictionary];

The little downside is that I have to provide some information about my custom types (see classesToConvert)…

Parsing JSON

Writing out JSON is one thing – parsing the other. To make a long story short – similar issues are present when trying to transform a JSON string to a rich object model… The JSON parser libraries expect an array (NSArray) or a map (NSDictionary). So we need something that is able to transform the NSDictionary into a rich object graph…

For that I have tested the KeyValueObjectMapping framework and also used the already discussed JAGPropertyConverter. Both function similar: With a little information about the used custom types, they are able to create an object graph out of the JSON data structures (NSArray, NSDictionary).

Github’s Mantle

An other interesting option could be Github’s Mantle. As stated on their project side, Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application. It also adresses the JSON issue, as it offers an automatic/magic externalRepresentation function that returns a NSDictionary of the object (its state). However, your classes now needs to inherit from the MTLModel class:

@interface MantleTask : MTLModel

@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* desc;

@end

Getting the map/NSDictonary of one class is simple and generating the JSON is easy too:

MantleTask* task = [[MantleTask alloc] init];
task.title = @"Some task";
task.desc = @"lot's of workz";

NSData* encodedData = [NSJSONSerialization
    dataWithJSONObject:[task externalRepresentation]
    options:NSJSONWritingPrettyPrinted error:nil];

NSString* jsonString = [[NSString alloc] initWithData:encodedData encoding:NSUTF8StringEncoding];

The pretty JSON output looks like:

{
  "title" : "Some task",
  "desc" : "lot's of workz"
}

The Mantle framework also supports nested classes, but you have to also include some type-mapping information as well:

@implementation MantleEmployee
+ (NSValueTransformer *)tasksTransformer {
    return [NSValueTransformer
       mtl_externalRepresentationArrayTransformerWithModelClass:MantleTask.class];
}
@end

This class function knows about it’s subtypes… So again some information on the actual type system is needed.

Summary

Generally the Mantle Framework provides a (somewhat) clean solution for:

  • generating maps (->JSON) out of an object graph
  • generating object graphs from maps (->JSON)

Of course, Mantle also supports the case where the external representation (e.g. JSON) changes – you simple implement some mapping function, as shown on their project side.

So far Mantle looks like a great library when transforming objects to an external representation. I also appreciate the dynamic/magic support for creating an object from a map:

NSData* data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* parsedObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

MantleEmployee* parsedEmp = [[MantleEmployee alloc] initWithExternalRepresentation:parsedObject];

Similar to the above shown “externalRepresentation” function, which creates a NSDictionary of my object graph!

About these ads

Howdy!

Posted in aerogear

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 34 other followers

%d bloggers like this: