views:

3174

answers:

4

I have a core data object graph (consisting of two entities linked by a to-many relationship).

I was curious, as a relatively inexperienced iPhone developer, whether anyone could recommend an approach, and a suitable JSON implementation for the iPhone, which would allow me to:

1) convert the core data records into a JSON string (whilst maintaining the relationship between the entities); and

2) convert the JSON string back into core data objects (again preserving the relationship between entities).

I have searched, unsuccessfully, for a tutorial/code sample on this point so any assistance would be gratefully received.

+15  A: 

First, pick a JSON library to use, I personally like TouchJSON but several others out there are quite nice as well. The complicated part, although not very hard, is to convert your managed objects into suitable structures for the conversion. I wrote this real quick so it may have an error or two :)

The methods you call are:

- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects;
- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc;

And the implementation is as follows:

- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
  NSDictionary *attributesByNamme = [[managedObject entity] attributesByName];
  NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
  NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
  [valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];
  for (NSString *relationshipName in [relationshipsByName allKeys]) {
    NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
    if (![description isToMany]) {
      [valuesForDictionary setValue:[self dataStructureForManagedObject:]];
      continue;
    }
    NSSet *relationshipObjects = [managedObject objectForKey:relationshipName];
    NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
    for (NSManagedObject *relationshipObject in relationshipObjects) {
      [relationshipArray addObject:[self dataStructureForManagedObject:relationshipObject]];
    }
    [valuesDictionary setObject:relationshipArray forKey:relationshipName];
  }
  return [valuesDictionary autorelease];
}

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
  NSMutableArray *dataArray = [[NSArray alloc] init];
  for (NSManagedObject *managedObject in managedObjects) {
    [dataArray addObject:[self dataStructureForManagedObject:managedObject]];
  }
  return [dataArray autorelease];
}

- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
  NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
  NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
  return jsonString;
}

- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc
{
  NSString *objectName = [structureDictionary objectForKey:@"ManagedObjectName"];
  NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
  [managedObject setValuesForKeysWithDictionary:structureDictionary];

  for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
    NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
    if (![description isToMany]) {
      NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
      NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
      [managedObject setObject:childObject forKey:relationshipName];
      continue;
    }
    NSMutableSet *relationshipSet = [managedObject mutableSetForKey:relationshipName];
    NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
    for (NSDictionary *childStructureDictionary in relationshipArray) {
      NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
      [relationshipSet addObject:childObject];
    }
  }
  return managedObject;
}

- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc
{
  NSError *error = nil;
  NSArray *structureArray = [[CJSONDeserializer deserializer] deserializeAsArray:json error:&error];
  NSAssert2(error == nil, @"Failed to deserialize\n%@\n%@", [error localizedDescription], json);
  NSMutableArray *objectArray = [[NSMutableArray alloc] init];
  for (NSDictionary *structureDictionary in structureArray) {
    [objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
  }
  return [objectArray autorelease];
}

Now this is recursive so you can easily end up translating your entire persistent store if you are not careful. Watch your relationships and make sure that they only go "down" the object tree so that you only get the objects you want translated.

Marcus S. Zarra
Thank you again for another excellent answer and for your very helpful book! :)
Urizen
Hi Marcus. I've just tried the code above (with some minor amendments to make it compile and the execution seems to go on indefinitely until the app crashes). Sorry to bother you but I was curious if you could perhaps point me in the right direction for solving this problem. It seems to happen with the recursion in the datastructureFromManagedObject method...
Urizen
Depends on your data structure. If your model will produce a loop then it will run forever. Review your data model and either make sure it is a tree design or put logic stops in the recursive code to prevent looping.
Marcus S. Zarra
Thanks for your response. I think that I've managed to solve the endless recursion problem but the code now throws an exception which seems to suggest that NSDate attributes cannot be serialised (i.e. Terminating app due to uncaught exception 'NSGenericException', reason: 'Cannot serialize data of type '__NSCFDate'') Is this the case? If so, is there any way around this?
Urizen
I've got round the above-mentioned problem by storing the value returned by [date timeIntervalSince1970] as an NSNumber.
Urizen
Clean up what? If you are new to Core Data then you should be grasping the basics of KVC/KVO before trying to tackle recursive code like this. There is nothing dirty about this code, it is a clean example. It's major flaw is that it does not handle edge cases; which would make the code *harder* to read.
Marcus S. Zarra
+2  A: 

Synchronizing Core Data with Rails is a detailed presentation that includes sample code for serializing/deserializing your Core Data objects to/from JSON (skip to slide 55 for the Core Data part). His sample code assumes a fairly simple model without relationships, though I think it would be pretty easy to extend.

The presentation also goes into some detail about keeping your Core Data model in sync with a REST-based web application, with pointers to some useful libraries, including ObjectiveResource and ASIHTTPRequest. Not sure if that's what you're trying to do, but it's worth a look even for the Core Data code.

chrispix
+2  A: 

I just wanted to point out a small typo, that caused the code to crash, and hopefully this will save you a few min.

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {

    NSMutableArray *dataArray = [[NSArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return [dataArray autorelease];
}

The NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray

really should be NSMutableArray *dataArray = [[NSMutableArray alloc] init];

that is all.

thank you

iAm
A: 

hello, thanks for the awesome code...

i have problems to stop the recursive code. could you give an example how to do that? up to now, i tried to count the elements, which i now that i have 4, and tried to stop the code when there are more than 4...

andy
This is a question, not an answer. Stack Overflow does not work like a Web forum.
Shaggy Frog