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

Deserialization into Instances

pashields edited this page Jan 31, 2012 · 1 revision

In addition to creating new instances as part of the deserialization process, ClassMapper can be used to update an existing instance. This is semantically much more complex than creating a fresh instance, but can be useful. That being said, if you don't need it, you will probably be happier without it.

Deserializing into an existing object

Let's say that you have a simple user class for a chat room application:

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

Given an instance of User, you could update it with new data from some external service. Here's a quick example:

// Existing instance
User *elvis = [User new];
elvis.name = @"The King";
elvis.status = @"In the building";

// New Data
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"The King", @"name", 
                                                                @"Left the building", @"status", nil];

// Update instance
[ClassMapper deserialize:dict toInstance:elvis];
elvis.status; // @"Left the building"

There are a few non-obvious points here:

  • The deserialization actually mutated the elvis instance. It also returns the object, but if you don't want elvis mutated, you should make a deep copy of elvis first.
  • Even though the elvis.name did not change, the property will be set again as part of the deserialization. Any observers should be prepared to handle this.
  • Only the properties specified by the keys in dict will be updated. If there are additional properties in the instance but not the dictionary, they will not be modified by deserialization.

Deserializing into an existing incomplete object

Objects can still be created if they are needed. Examples only involving strings are not particularly illuminating here, as strings are always copied. Let's look at something more interesting:

@interface Location : NSObject
@property(nonatomic, strong)NSNumber *lat;
@property(nonatomic, strong)NSNumber *long;
@end
@interface Place : NSObject
@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)Location *location;
@end

// Existing instance
Place *place = [Place new];
place.name = @"The Pig";

// New Data
NSDictionary *locDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:70.0], @"lat",
                                                                   [NSNumber numberWithFloat:70.0], @"long",
                                                                   nil];
// Equivalent to {"name":"The Pig", "location":{"lat":70.0, "long":70.0}}
NSDictionary *placeDict = [NSDictionary dictionaryWithObjectsAndKeys:@"The Pig", @"name", 
                                                                     locDict, @"location", nil];

// Update instance
[ClassMapper deserialize:placeDict toInstance:place];
place.location.lat; // 70.0

Since place didn't have an existing instance in place.location, one was created at the time of deserialization. If place.location already existed, then that instance would have had it's lat and long properties reset.

Deserializing into existing collections

Deserializing into an instance of a collection is, if not ambiguous semantically, at least complex to reason about. On some level, we might want to always create a sync relationship between the collections. On the other hand, we might want the deserialization process to add the new elements into the collection (this corresponds to the semantic we have chosen for KVC objects). A third possibility is that we might not want to mutate the collection at all, but merely update the existing instances inside the collection.

Compounding these existing possibilities are the existence of both mutable and non-mutable collections inside of Cocoa (e.g. NSMutableArray and NSArray, respectively). We cannot add elements to or remove elements from non-mutable collections, so we can only choose one possible semantic: we can update the instances inside of the collection. Let's look at an example:

Deserializing into a non mutable collection

@interface Album : NSObject
@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)NSString *artist;
@end
@interface User : NSObject
@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)NSArray *topThree;
@end

// Existing instance
User *user = [User new];
user.name = @"Pat";

Album *one = [Album new];
one.name = @"Master of Reality";
one.artist = @"Black Sabbath";
...
Album *three = [Album new];
three.name = @"Pink Flag";
three.artist = @"Wire";

user.topThree = [NSArray arrayWithObjects:one, two, three];

// Discover new music - New Data
NSArray *cerealRay = [NSArray arrayWithObjects:
                          [NSDictionary dictionaryWithObjectsAndKeys:@"A Love Supreme",
                                 @"name", @"John Coltrane", @"artist", nil], 
                          [NSDictionary dictionaryWithObjectsAndKeys:@"Aqualung", @"name", 
                                 @"Jethro Tull", @"artist", nil],
                          [NSDictionary dictionaryWithObjectsAndKeys:@"I See A Darkness",
                                 @"name", @"Will Oldham", @"artist", nil],
                          nil];
NSDictionary *userDict = [NSDictionary dictionaryWithObjectsAndKeys:@"Pat", @"name", 
                                                                    cerealRay, @"topThree", nil];

// Update instance
[ClassMapper deserialize:userDict toInstance:user];
Album *album = [user.topThree objectAtindex:0];
album.name; // @"A Love Supreme"

So, what actually happened here is that for each instance of album in user.topThree, we mutated the instance according to the properties in that position in the input array. The array itself was not modified or replaced during this process. The objects in the serialized data were mapped to objects in the instance based on position. In the case where the instance contains and NSDictionary instead of an NSArray, like in the example, that matching will be done based on keys.

Deserializing into a mutable collection

In some cases, like the one presented above the mutability of the collection doesn't matter. When deserializing into existing instances of NSMutableArray/NSMutableDictionary, all existing data will be removed and then the collection will be refilled with the new data.

Some users may wish that instead of wiping the collection first, deserializing into an existing collection would have an additive effect. This is rather easy to construct yourself by serializing into the Class and then copying the new data. The current semantic is very hard to re-create if you only have "additive deserialization," so it was chosen as the default.

Clone this wiki locally