Serializing (Archiving/Unarchiving) an NSManagedObject Graph

Since the NSManagedObject class doesn’t conform to the NSCopying protocol, there seems to be no easy way to archive a bunch of interrelated managed objects. Once you archive a part of your object graph, you could use the serialized representation for various purposes – sending it over a network, making backups, etc.

The way I figured this could work is to make my NSManagedObject subclass somehow convert an instance of itself into an NSDictionary instance, which would hold all properties and relationships for that particular object. The NSDictionary class conforms to the NSCopying protocol, so once you have the dictionary, you can do whatever you like with it. Of course, you would also need an inverse mechanism that converts an NSDictionary instance into your NSManagedObject.

In order to make the process as transparent as possible, I figured I should create an NSManagedObject subclass, which would sit right between NSManagedObject and your model classes in the inheritance tree. A perfect solution would be able to convert a given object from/to a dictionary without knowing the actual type of the object being converted.

It turns out this solution is possible, and is pretty straightforward to implement. So, let’s call this class ExtendedManagedObject.

@interface ExtendedManagedObject : NSManagedObject {
    BOOL traversed;
}

@property (nonatomic, assign) BOOL traversed;

- (NSDictionary*) toDictionary;
- (void) populateFromDictionary:(NSDictionary*)dict;
+ (ExtendedManagedObject*) createManagedObjectFromDictionary:(NSDictionary*)dict
                                                   inContext:(NSManagedObjectContext*)context;

@end

So, the interface is pretty straightforward, we basically have two methods that do all the work for us, toDictionary and populateFromDictionary:. The createManagedObjectFromDictionary:inContext: class method is just a helper for creating new managed objects directly from a dictionary. Let’s see how each of these is implemented.

- (NSDictionary*) toDictionary
{
    self.traversed = YES;

    NSArray* attributes = [[[self entity] attributesByName] allKeys];
    NSArray* relationships = [[[self entity] relationshipsByName] allKeys];
    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:
                                 [attributes count] + [relationships count] + 1];

    [dict setObject:[[self class] description] forKey:@"class"];

    for (NSString* attr in attributes) {
        NSObject* value = [self valueForKey:attr];

        if (value != nil) {
            [dict setObject:value forKey:attr];
        }
    }

    for (NSString* relationship in relationships) {
        NSObject* value = [self valueForKey:relationship];

        if ([value isKindOfClass:[NSSet class]]) {
            // To-many relationship

            // The core data set holds a collection of managed objects
            NSSet* relatedObjects = (NSSet*) value;

            // Our set holds a collection of dictionaries
            NSMutableSet* dictSet = [NSMutableSet setWithCapacity:[relatedObjects count]];

            for (ExtendedManagedObject* relatedObject in relatedObjects) {
                if (!relatedObject.traversed) {
                    [dictSet addObject:[relatedObject toDictionary]];
                }
            }

            [dict setObject:dictSet forKey:relationship];
        }
        else if ([value isKindOfClass:[ExtendedManagedObject class]]) {
            // To-one relationship

            ExtendedManagedObject* relatedObject = (ExtendedManagedObject*) value;

            if (!relatedObject.traversed) {
                // Call toDictionary on the referenced object and put the result back into our dictionary.
                [dict setObject:[relatedObject toDictionary] forKey:relationship];
            }
        }
    }

    return dict;
}

First, this function enumerates all attributes and relationships of the object. It then uses key-value coding methods to fetch attribute values and store them into the dictionary object. It also stores its class string into the dictionary, so that the unpacker method knows which object to instantiate.

The procedure is a bit different for relationships, since they can be “to-one” and “to-many”. For “to-many” relationships, we need to fetch a set of all related objects, convert each of them into a dictionary recursively by calling toDictionary, and then add the newly created dictionary to our object’s own dictionary representation.

For “to-one” relationships the process is simpler – we need to check if the object has already been traversed (to prevent infinite recursion), and if not, we convert it to a dictionary and add it to our object’s dictionary representation.

Note: the traversed variable is a very primitive way to check for recursion, as it will only work if you convert an object to a dictionary once in its lifetime. Every subsequent call to toDictionary would only create a shallow dictionary, containing only attributes of the object, since all of its related objects have traversed set to true. The proper implementation is left as an exercise for the reader. ;)

So, on with the methods for converting a dictionary to a managed object:

- (void) populateFromDictionary:(NSDictionary*)dict
{
    NSManagedObjectContext* context = [self managedObjectContext];

    for (NSString* key in dict) {
        if ([key isEqualToString:@"class"]) {
            continue;
        }

        NSObject* value = [dict objectForKey:key];

        if ([value isKindOfClass:[NSDictionary class]]) {
            // This is a to-one relationship
            ExtendedManagedObject* relatedObject =
                [ExtendedManagedObject createManagedObjectFromDictionary:(NSDictionary*)value
                                                               inContext:context];

            [self setValue:relatedObject forKey:key];
        }
        else if ([value isKindOfClass:[NSSet class]]) {
            // This is a to-many relationship
            NSSet* relatedObjectDictionaries = (NSSet*) value;

            // Get a proxy set that represents the relationship, and add related objects to it.
            // (Note: this is provided by Core Data)
            NSMutableSet* relatedObjects = [self mutableSetValueForKey:key];

            for (NSDictionary* relatedObjectDict in relatedObjectDictionaries) {
                ExtendedManagedObject* relatedObject =
                    [ExtendedManagedObject createManagedObjectFromDictionary:relatedObjectDict
                                                                   inContext:context];
                [relatedObjects addObject:relatedObject];
            }
        }
        else if (value != nil) {
            // This is an attribute
            [self setValue:value forKey:key];
        }
    }
}

+ (ExtendedManagedObject*) createManagedObjectFromDictionary:(NSDictionary*)dict
                                                   inContext:(NSManagedObjectContext*)context
{
    NSString* class = [dict objectForKey:@"class"];
    ExtendedManagedObject* newObject =
        (ExtendedManagedObject*)[NSEntityDescription insertNewObjectForEntityForName:class
                                                              inManagedObjectContext:context];

    [newObject populateFromDictionary:dict];

    return newObject;
}

The createManagedObjectFromDictionary:inContext: method first creates an ExtendedManagedObject instance, using the “class” key to extract class information, and inserts it into the managed object context. It then calls populateFromDictionary:, which iterates through all keys in the dictionary and checks the type of object returned – it can be a simple attribute, a to-one relationship, or a to-many relationship.

The trick that does most of the dirty Core Data work is a call to [self mutableSetValueForKey:key]. It returns a proxy set summoned by the Core Data subsystem, which does all the database magic for you when you add objects to it. So when you call addObject: on a proxy set object, you’re actually establishing a relationship between the object being added to the set and the owner of the proxy set. Cool stuff!

So there you go, all you have to do is make each of your model classes a subclass of ExtendedManagedObject instead of NSManagedObject, and you can use toDictionary, populateFromDictionary: and createManagedObjectFromDictionary:inContext: to play around with your managed objects. For example, if you have a Person entity in your model, your header file for that class might look something like this:

@interface Person : NSManagedObject {
}

@property (nonatomic, retain) NSString* name;

@end

Your Person class needs to be a subclass of ExtendedManagedObject, so you need to import the ExtendedManagedObject.h header file, and change “NSManagedObject” to “ExtendedManagedObject”:

#import "ExtendedManagedObject.h"

@interface Person : ExtendedManagedObject {
}

@property (nonatomic, retain) NSString* name;

@end

Now that your Person class is a subclass of ExtendedManagedObject, you can convert a Person instance to a dictionary with:

Person* person = ... // fetch a person from the core data store, as usual
NSDictionary* personDict = [person toDictionary];

To do the inverse, you need to call the createManagedObjectFromDictionary:inContext method, but since it’s a class method, you need to invoke it statically:

Person* person = ... // fetch a person from the core data store, as usual
NSDictionary* personDict = [person toDictionary];

NSManagedObjectContext* context = ... // your managed object context reference
Person* anotherPerson = [ExtendedManagedObject createManagedObjectFromDictionary:personDict inContext:context];

The above code would convert person to a dictionary (personDict), and then use that dictionary to create a new Person object in the context and assign it to anotherPerson. If you save the context at this point, your store will contain two identical Person objects.

You can also update an existing managed object using the populateFromDictionary: method:

NSDictionary* personDict = ... // Your updated person dictionary (fetched from a web service, for example)
NSManagedObject* person = ... // Fetch your Person managed object from a context

// Update the person managed object using data from the dicitonary
[person populateFromDictionary:personDict];

You can check out the entire code from this gist on Github.

In my next post, I’ll discuss a simple way to load your SQLite database with predefined data when your application is first launched. Stay tuned.

UPDATE: PCKLsoft has posted an updated version of the above gist, thanks man! It turns ExtendedManagedObject int a category on NSManagedObject (much more elegant) and deals with the traversed flag. See his comments for details.

UPDATE 2: nuthatch has yet another improvement on that gist, check it out. Thanks nuthatch.

50 responses to “Serializing (Archiving/Unarchiving) an NSManagedObject Graph”

  1. Konjanik

    Lepo si ga picnuo:)

  2. Ian

    Nice, I’m looking for something to do exactly this.

    I think you have an error in the toDictionary method where InspectionsManagedObject is supposed to be ExtendedManagedObject.

    A small example project using this with NSCoding would be great, too. :D

    Thanks, Ian

  3. Ian

    No problem, thanks for sharing the code.

    I’m noticing something else as well. I have a tree of custom NSManagedObject subclasses, and the toDictionary method fails when it reaches an object which is faulting. Here is the error:

    2010-03-30 10:36:35.120 CourseBuilder[1429:a0f] -[NSManagedObject traversed]: unrecognized selector sent to instance 0x1a97e090
    (gdb) po 0x1a97e090
    (entity: CBQuestion; id: 0x1a97dc70 ; data: )

    Have you run into this with larger object graphs?

  4. skarface

    I didn’t understand the concluding part of your article, could you please explain it more?

  5. Mike

    This is fantastic – very impressed and happy to find this. The toDictionary is working beautifully for me but I haven’t slept in 2 days and I am stuck on the inverse. What am I calling the:

    createManagedObjectFromDictionary:(NSDictionary*)dict inContext:(NSManagedObjectContext*)context

    on? i.e. for the toDictionary I used:

    NSDictionary *dict = [self.object toDictionary];

    What’s the line for the inverse? Thanks again!

  6. Matt Coneybeare

    great code. you should consider throwing this up on github

  7. Viet Tran

    Very nice. Clean. Elegant. Effective. Save me tons of hours. We need more people like you to make Cocoa a better framework.

  8. Adam

    Wow, this is great. Just what I was looking for!

    I’ll be trying this to ultimately send data from one phone to another. I have a question that goes to methodology or design. Once I have my dictionary loaded in memory would I even need to store the dict to file (assuming a small data set)?
    Thanks so much!

  9. Dillip

    Hi Vladimir,
    I am a newbie in iPhone and I think, your code would be useful for my requirement.
    I would appreciate if you could send me bit more explanation on below lines along with a exact example..

    NSManagedObjectContext* context = … // your managed object context reference
    Person* anotherPerson = [ExtendedManagedObject createManagedObjectFromDictionary:personDict inContext:context];

    Where this ‘context’ object will come from?

    Thanks in advance..

    Regds,
    Dillip

  10. Dillip

    Hi Vladimir,
    Thank you so much for your prompt response. I really appreciate it. I have created my project as ‘View-based Application’, so I didn’t have any option such as ‘use Core Data for storage’ to select.

    In this current scenario, can I implement NSManagedObjectConect* in my appDelegate class? ( I know this is out of scope question..) Mine is iPhone project

    Thanks again for your time..

    Best,
    Dillip

  11. Will Johnston

    Vladimir – please check out:
    http://gist.github.com/611157
    Thanks for your help.
    : )

  12. bob

    for (NSString* key in [dict allKeys])

    should be written as

    for (NSString* key in dict)

  13. Brian

    Is there a way to serialize more than once? It seems like once the traversed bools for an object tree are set to YES, the only way to get it to start over is to manually traverse the tree and set them to NO. I was planning on writing a “reset” method to do something like that and have a new public toDictionary that called reset then your toDictionary. If you can think of an easier way, I’d love to hear it.

  14. Alexander Donchev

    I am using your code and I am getting some weird results. For example I start with [NSManaged Object toDictionary] and then I use TouchJSON to convert the dictionary to JSON. My NSManaged Object has two relations activities and favorite_locations. One gets quotation marks around it the other doesn’t. And touchJSON fails when it reaches {(\n)}. Do you have any suggestions on how to fix this, I don’t know if this is a bug in TouchJSON, or in your code or in my data model, my app has been working with no problems so I don’t think it is the data model but there might be something that I am missing. Could you help me?

    (gdb) po myDictionary
    {
    activities = “{(\n)}”;
    class = Kid;
    “favorite_locations” = “{(\n)}”;
    “first_name” = “Test User”;
    }

  15. Alexander Donchev

    Here is an activity object that is converted toDictionary. After the conversion:
    (gdb) po activityDict
    {
    “activity_info” = “Testing 1″;
    “activity_location” = {
    address = “3536 N Paulina St, Chicago, United States, 60657″;
    name = Location1;
    };
    class = Activity;
    “end_time” = “2011-03-22 00:25:33 +0000″;
    participants = “{(\n {\n class = Participant;\n \”first_name\” = Susan;\n \”last_name\” = Smith;\n \”parent1_name\” = \”Ann Smith\”;\n \”parent1_number\” = \”773-123-4567\”;\n \”phone_number\” = \”888-123-4567\”;\n} \n)}”;
    “start_time” = “2011-03-21 23:25:33 +0000″;
    “sync_status” = new;
    }

    Now if I attempt to serialize this with TouchJSON it fails at this combination {(\n {\n
    If I don’t specify any participants I end up with participants = {(\n)}”; and TouchJSON fails again.

    This relation is as follows: an activity has many participants. Each participant has a few attributes such as first_name, last_name ….

    TouchJSON fails because every time it encounters “{…}” the code jumps a function that serializes an NSDictionary, and it starts to look for keys.
    The results of [object toDictionary]; for to many relationship is
    {( {…} {…} )} if it is like ( {…} {…} ) it might work with TouchJSON but I don’t know how to edit your code so I can test this. Any suggestions?

  16. Alexander Donchev

    [act addParticipants:[NSSet setWithArray:contacts]]; – This is how I add participants.

  17. Alexander Donchev

    I think, I found what the problem is. In your code you are creating an NSSet for To-Many-Relations and touchJson does not handle NSSet so it fails. Now I have to decide what to change, make your code save that relationship as NSArray or change TouchJSON, I added something like this, need to figure out what to do exactly with the NSSet. Which way would be easier to implement. If I change the NSSet in your code to NSArray, how would this impact the conversion from NSDictionary back to Extended Managed Object?
    else if ([inObject isKindOfClass:[NSSet class]])
    {
    //Do something
    NSLog(@”NSSet”);
    }

  18. Xed

    hi,

    I’m using SBJason (json-framework) to convert dictionary into json representation and all I get is a NULL.
    is this a similar problem to Alexander’s? i.e. Do I have to change the code around to get it working?

    thanks.

  19. Xed

    ah, that worked for me. thanks.

    One question though, how do I ‘update’ an existing core data object as opposed to creating a new one?

    thanks.

  20. Nick Tolomiczenko

    Hi Vladimir,

    First of all, I love the nice clean code.

    I just want to point out a little bug however.

    The code, above, and the code I checked out from github both had a bug in the method, populateFromDictionary. I believe references to ‘newObject’ should be changed to ‘self’.

    Nick.

  21. Tony

    Can you serialize a NSArray of NSManagedObjects using this?

  22. international law blog

    I serialized some objects using Binary Formatter in C#. Now I need to erase a certain object from that file. I know where it starts, so I can seek to it’s beginning and end. Just I don’t know how can I erase that object. Any Suggestions will be very useful.
    Regards
    Daniel

  23. bedroom design

    Hi
    Vladimir’s Code Snippets
    i would like to say thank you about this cool post,and this idea very helpful to me,
    Thank You

  24. vichu

    Vladimir:

    Your code is very elegant — appreciate it.

    do you have any ideas on how to update the dictionary with changes since last “toDictionary” call, instead of recreating the dictionary from scratch again?

    thx

  25. PKCLsoft

    Thanks so much for this. It’s basically what I figured I’d have to write, but done very nicely, and well documented and supported.

  26. PKCLsoft

    Actually, whilst I haven’t actually run it yet, I’ve converted your subclass into a category. I prefer not to change the NSManagedObject classes that are generated by Xcode.

    In doing this I’ve also (I think) dealt with the traversed flag and the possible need to serialize objects more than once.

    Once I’ve tested it, can I post it here, or the gist?

  27. PKCLsoft

    Got it working, but I have an issue whereby the newly created object ends up creating duplicates of all of the objects in it’s graph. So all objects in it’s attributes, and relationships are re-created which is obviously undesirable if you want to import an object into a database that may also be related to some overlapping objects.

    I also changed the code to handle NSDate and to use NSArray instead of NSSet internally so that NSJSONSerializer can be used to serialise the dictionaries. Personally, I like to just export the disctionaries using writeTo…, but some people prefer JSON to XML.

    Really nice starting point though. Do you have any suggestions for dealing with the filtering out of duplicate objects? I was thinking of creating a NSPredicate using the keys/attributes of each object before creating it, and checking the database for another object that is exactly the same. I just don’t yet know how to do so.

  28. PKCLsoft

    Here’s a link to my fork of your gist. This is everything except my attempts to filter imported duplicate objects.

    http://gist.github.com/pkclsoft/4958148

  29. nuthatch

    I’ve forked PKCLsoft’s gist to address some issues with this convenient category

    https://gist.github.com/nuthatch/5607405

  30. metabren

    Just wanted to say thanks. Using it in my project. Saved me a heap of time! :)

  31. Todd

    Did you consider a Category as opposed the need to subclass NSManagedObject?

Leave a Reply