Skip to content
This repository was archived by the owner on Aug 30, 2019. It is now read-only.

Deserialization of Dictionaries

pashields edited this page Jan 29, 2012 · 1 revision

ClassMapper allows you to convert NSDictionaries into your objects. The purpose of this is to allow you to more easily bridge between your models and your external serialization (e.g. JSON).

Deserializing into your objects

In order to deserialize into one of your objects, your class must be KVC compliant. For each key in the dictionary of serialized data, ClassMapper will set the value fo the property. Let's look at a simple example, using only scalars from the supported class list.

Simple Scalars

Let's suppose you have an object for your Social Beer program:

@interface Beer : NSObject
@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)NSNumber *rank;
@end

If we want to use ClassMapper to create an instance of Beer with the name "Pabst Blue Ribbon" and the rank 1, we can run the following:

// Equivalent to Beer *beer = [Beer new]; beer.name=@"Pabst Blue Ribbon"; beer.rank = [NSNumber numberWithInt:1];
[ClassMapper deserialize:[NSDictionary dictionaryWithObjectsAndKeys:@"Pabst Blue Ribbon", @"name",
                                                                [NSNumber numberWithInt:1], @"rank", nil]
                 toClass:[Beer class]];

And voila, we have our object.

Nested Objects

Now, let's add another model into the mix:

@interface User : NSObject
@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)Beer *beer;
@end

The good news here is that the naive solution works. If we try:

NSDictionary *beer = [NSDictionary dictionaryWithObjectsAndKeys:@"Pabst Blue Ribbon", 
                     @"name", [NSNumber numberWithInt:1], @"rank", nil];
// Equiv to {"beer":{"name":"Pabst Blue Ribbon", "rank":1}, "name":"He-Man"}
NSDictionary *user = [NSDictionary dictionaryWithObjectsAndKeys:beer, @"beer", @"He-Man", @"name", nil];
[ClassMapper deserialize:user toClass:[User class]];

We get back an instance of User user, where user.beer is an instance of the Beer class just like we created above. ClassMapper will read your property meta-data and create instances of the appropriate type for you.

Dictionaries inside your objects

You can also use dictionaries within your classes. Let's say we had a class like:

// Note: this is a very bad design, but a useful example, like armadillos
@interface Name : NSObject
@property(nonatomic, strong)NSDictionary *parts;
@end

Which you use to create instances like so:

Name *name = [Name new];
name.parts = [NSDictionary dictionaryWithObjectsAndKeys:@"Conan", @"first", @"the", @"middle",
                                                        @"Barbarian", @"last", nil];

We can create the same instance using ClassMapper by just nesting our dictionaries:

NSDictionary *parts = [NSDictionary dictionaryWithObjectsAndKeys:@"Conan", @"first", @"the", @"middle",
                                                                 @"Barbarian", @"last", nil];
NSDictionary *nameDict = [NSDictionary dictionaryWithObjectsAndKeys:parts, @"parts"];
[ClassMapper deserialize:nameDict toClass:[Name class]];

ClassMapper reads the meta information about the parts property, and just like before creates a new NSDictionary with that state.

Your objects inside that dictionary

Unfortunately, NSDictionaries don't have the same metadata that your classes do, so we have a bit of an issue if we have complex objects inside the dictionary. The above example works because we only use NSStrings, which don't have any objects inside of them (and are thus terminal nodes in (de)serialization). If we encounter a dictionary inside of another dictionary, then we get some ambiguity. For instance:

@interface SocialNetworkExample : NSObject
@property(nonatomic, strong)NSString *ex;
@end
@interface Keyword : NSObject
@property(nonatomic, strong)NSDictionary *networkExamples;
@end
NSDictionary *twitterExDict = [NSDictionary dictionaryWithObjectsAndKeys:@"boo #sopa", @"ex", nil];
NSDictionary *examplesDict = [NSDictionary dictionaryWithObjectsAndKeys:twitterExDict, @"Twitter", nil];
// Equivalent to {"networkExamples":{"Twitter":{"ex":"boo #sopa"}}}
NSDictionary *keywordDict = [NSDictionary dictionaryWithObjectsAndKeys:examplesDict, @"networkExamples", nil];
Keyword *keyword = [ClassMapper deserialize:keywordDict toClass:[Keyword class]];
id ex = [keyword.networkExamples objectForKey:@"Twitter"];

So is ex an instance NSDictionary or an instance of SocialNetworkExample? In this case, it will be an NSDictionary. But, we can use the MapperConfig to help us here!

[[MapperConfig sharedInstance] mapKey:@"Twitter" toClass:[SocialNetworkExample class]];
Keyword *keyword = [ClassMapper deserialize:keywordDict toClass:[Keyword class]];
SocialNetworkExample *ex = [keyword.networkExamples objectForKey:@"Twitter"];
// ex.ex == @"boo #sopa"

Now this only really helps us when we can predict the keys. ClassMapper currently doesn't support dictionary level type information, so for now, if you don't know the keys in advance you'll have to stick with dictionaries.

Clone this wiki locally