-
-
Save nuthatch/5607405 to your computer and use it in GitHub Desktop.
@interface NSManagedObject (Serialization) | |
- (NSDictionary*) toDictionary; | |
- (void) populateFromDictionary:(NSDictionary*)dict; | |
+ (NSManagedObject*) createManagedObjectFromDictionary:(NSDictionary*)dict | |
inContext:(NSManagedObjectContext*)context; | |
@end |
// | |
// see https://gist.github.com/pkclsoft/4958148 by Peter Easdown | |
// based upon http://vladimir.zardina.org/2010/03/serializing-archivingunarchiving-an-nsmanagedobject-graph/ | |
// | |
#import "NSManagedObject+Serialization.h" | |
@implementation NSManagedObject (Serialization) | |
#define DATE_ATTR_PREFIX @"dAtEaTtr:" | |
#warning "Change CLASS_PREFIX if it's not ABC" | |
#define CLASS_PREFIX @"ABC" | |
#pragma mark - | |
#pragma mark Dictionary conversion methods | |
- (NSDictionary*) toDictionaryWithTraversalHistory:(NSMutableSet*)traversalHistory { | |
NSArray* attributes = [[[self entity] attributesByName] allKeys]; | |
NSArray* relationships = [[[self entity] relationshipsByName] allKeys]; | |
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity: | |
[attributes count] + [relationships count] + 1]; | |
NSMutableSet *localTraversalHistory = nil; | |
if (traversalHistory == nil) { | |
localTraversalHistory = [NSMutableSet setWithCapacity:[attributes count] + [relationships count] + 1]; | |
} else { | |
localTraversalHistory = traversalHistory; | |
} | |
[localTraversalHistory addObject:self]; | |
[dict setObject:[[self class] description] forKey:@"class"]; | |
for (NSString* attr in attributes) { | |
NSObject* value = [self valueForKey:attr]; | |
if (value != nil) { | |
if ([value isKindOfClass:[NSDate class]]) { | |
NSTimeInterval date = [(NSDate*)value timeIntervalSinceReferenceDate]; | |
NSString *dateAttr = [NSString stringWithFormat:@"%@%@", DATE_ATTR_PREFIX, attr]; | |
[dict setObject:[NSNumber numberWithDouble:date] forKey:dateAttr]; | |
} else { | |
[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 | |
NSMutableArray* dictSet = [NSMutableArray arrayWithCapacity:[relatedObjects count]]; | |
for (NSManagedObject* relatedObject in relatedObjects) { | |
if ([localTraversalHistory containsObject:relatedObject] == NO) { | |
[dictSet addObject:[relatedObject toDictionaryWithTraversalHistory:localTraversalHistory]]; | |
} | |
} | |
[dict setObject:[NSArray arrayWithArray:dictSet] forKey:relationship]; | |
} | |
else if ([value isKindOfClass:[NSOrderedSet class]]) { | |
// To-many relationship | |
// The core data set holds an ordered collection of managed objects | |
NSOrderedSet* relatedObjects = (NSOrderedSet*) value; | |
// Our ordered set holds a collection of dictionaries | |
NSMutableArray* dictSet = [NSMutableArray arrayWithCapacity:[relatedObjects count]]; | |
for (NSManagedObject* relatedObject in relatedObjects) { | |
if ([localTraversalHistory containsObject:relatedObject] == NO) { | |
[dictSet addObject:[relatedObject toDictionaryWithTraversalHistory:localTraversalHistory]]; | |
} | |
} | |
[dict setObject:[NSOrderedSet orderedSetWithArray:dictSet] forKey:relationship]; | |
} | |
else if ([value isKindOfClass:[NSManagedObject class]]) { | |
// To-one relationship | |
NSManagedObject* relatedObject = (NSManagedObject*) value; | |
if ([localTraversalHistory containsObject:relatedObject] == NO) { | |
// Call toDictionary on the referenced object and put the result back into our dictionary. | |
[dict setObject:[relatedObject toDictionaryWithTraversalHistory:localTraversalHistory] forKey:relationship]; | |
} | |
} | |
} | |
if (traversalHistory == nil) { | |
[localTraversalHistory removeAllObjects]; | |
} | |
return dict; | |
} | |
- (NSDictionary*) toDictionary { | |
// Check to see there are any objects that should be skipped in the traversal. | |
// This method can be optionally implemented by NSManagedObject subclasses. | |
NSMutableSet *traversedObjects = nil; | |
if ([self respondsToSelector:@selector(serializationObjectsToSkip)]) { | |
traversedObjects = [self performSelector:@selector(serializationObjectsToSkip)]; | |
} | |
return [self toDictionaryWithTraversalHistory:traversedObjects]; | |
} | |
+ (id) decodedValueFrom:(id)codedValue forKey:(NSString*)key { | |
if ([key hasPrefix:DATE_ATTR_PREFIX] == YES) { | |
// This is a date attribute | |
NSTimeInterval dateAttr = [(NSNumber*)codedValue doubleValue]; | |
return [NSDate dateWithTimeIntervalSinceReferenceDate:dateAttr]; | |
} else { | |
// This is an attribute | |
return codedValue; | |
} | |
} | |
- (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 | |
NSManagedObject* relatedObject = | |
[NSManagedObject createManagedObjectFromDictionary:(NSDictionary*)value | |
inContext:context]; | |
[self setValue:relatedObject forKey:key]; | |
} | |
else if ([value isKindOfClass:[NSArray class]]) { | |
// This is a to-many relationship | |
NSArray* relatedObjectDictionaries = (NSArray*) 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) { | |
NSManagedObject* relatedObject = | |
[NSManagedObject createManagedObjectFromDictionary:relatedObjectDict | |
inContext:context]; | |
[relatedObjects addObject:relatedObject]; | |
} | |
} | |
else if ([value isKindOfClass:[NSOrderedSet class]]) { | |
// This is a to-many relationship | |
NSArray* relatedObjectDictionaries = (NSArray*) value; | |
// Get a proxy set that represents the relationship, and add related objects to it. | |
// (Note: this is provided by Core Data) | |
NSMutableOrderedSet* relatedObjects = [self mutableOrderedSetValueForKey:key]; | |
for (NSDictionary* relatedObjectDict in relatedObjectDictionaries) { | |
NSManagedObject* relatedObject = | |
[NSManagedObject createManagedObjectFromDictionary:relatedObjectDict | |
inContext:context]; | |
[relatedObjects addObject:relatedObject]; | |
} | |
} | |
else if (value != nil) { | |
if ([key hasPrefix:DATE_ATTR_PREFIX] == NO) | |
[self setValue:[NSManagedObject decodedValueFrom:value forKey:key] forKey:key]; | |
else { | |
// the entity Transaction is not key value coding-compliant for the key "dAtEaTtr:timestamp". | |
NSString *originalKey = [key stringByReplacingOccurrencesOfString:DATE_ATTR_PREFIX withString:@""]; | |
[self setValue:[NSManagedObject decodedValueFrom:value forKey:key] forKey:originalKey]; | |
} | |
} | |
} | |
} | |
+ (NSManagedObject*) createManagedObjectFromDictionary:(NSDictionary*)dict | |
inContext:(NSManagedObjectContext*)context | |
{ | |
NSString* class = [dict objectForKey:@"class"]; | |
// strip off class prefix, if the names in our data model don't match the class names! | |
NSString* name = [class stringByReplacingOccurrencesOfString:CLASS_PREFIX withString:@""]; | |
NSManagedObject* newObject = | |
(NSManagedObject*)[NSEntityDescription insertNewObjectForEntityForName:name | |
inManagedObjectContext:context]; | |
[newObject populateFromDictionary:dict]; | |
return newObject; | |
} | |
@end |
This serialisation doesn't seem to fix up object references. e.g. you end up with duplicated objects in your object graph that isn't faithful to the original object graph.
This can be demonstrated by simple 'diamond' shaped object graph:
- A ----> B (To-One relationship)
- A -->> C (To-Many relationship)
- B ----> D (To-One relationship)
- C ----> D (To-One relationship)
Assume that a
, b
, c
and d
are objects of the respective entities defined above, and they hold precisely those relationships with each other as well.
If we try to serialize an a
, we end up with TWO copies of the serialisation of d
, because there'll be one coming out of the relation with b
and one out of c
. When deserialized, the object graph looks like
a'
--> b'
a'
--> c'
b'
--> d'1
c'
--> d'2
Which is unacceptable. How can we improve this gist further to preserve object references?
The date prefixing is not necessary these days, surely, since Core Data supports Date
as a data type.
Thanks for this , much better than the solution I have! I'm still getting duplicate objects created though so need to implement serializationObjectsToSkip but not sure how this is done?