可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
convert the core data records into a JSON string (whilst maintaining the relationship between the entities); and
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.
回答1:
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 *attributesByName = [[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 = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];
if (![description isToMany]) {
NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
[valuesDictionary setObject:[self dataStructureForManagedObject:relationshipObject] forKey:relationshipName];
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 = [[NSMutableArray 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.
回答2:
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
回答3:
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.
回答4:
If you have an NSDate
in your managed object, as mentioned above in one of the comments, you\'ll have problems serializing the object containing the NSDate
. A simple fix is to add a JSONDataRepresentation
method to NSDate
using objective-c categories.
Add these two files to your project:
NSdate.h:
#import <Foundation/Foundation.h>
@interface NSDate (jsondatarepresentation)
- (NSData*) JSONDataRepresentation;
@end
NSDate.m:
#import \"NSDate.h\"
@implementation NSDate (jsondatarepresentation)
- (NSData*) JSONDataRepresentation {
return [[[NSNumber numberWithDouble:[self timeIntervalSince1970]] stringValue] dataUsingEncoding:NSUTF8StringEncoding];
}
@end
回答5:
There is a lib that does the JSON synchronization for you : https://github.com/sixdegrees/lidenbrock
回答6:
I came across this post which works very well.
http://touchalicious.com/blog/2009/10/25/turn-core-data-models-into-json.html
Since this is recursive, many-to-many relationships are going to keep looping through themselves. To avoid this, I added an \"isExportable\" key to the user info dictionary of the relationships in my Core Data model. You can then check for this key and choose to not loop through relationships without it.
if ([property isKindOfClass:[NSRelationshipDescription class]])
{
NSRelationshipDescription *relationshipDescription = (NSRelationshipDescription *)property;
if ([[[relationshipDescription userInfo] objectForKey:@\"isExportable\"] boolValue] == YES)
{
NSString *name = [relationshipDescription name];
if ([relationshipDescription isToMany])
{
NSMutableArray *arr = [properties valueForKey:name];
if (!arr)
{
arr = [[NSMutableArray alloc] init];
[properties setValue:arr forKey:name];
}
for (NSManagedObject *o in [self mutableSetValueForKey:name])
{
[arr addObject:[o propertiesDictionary]];
}
}
else
{
NSManagedObject *o = [self valueForKey:name];
[properties setValue:[o propertiesDictionary] forKey:name];
}
}
}
}
回答7:
Just thought id post a quick update to this question. I followed the Answers by Marcus and Brandon and came up with this for JSON exporting (it uses TouchJSON still):
- (NSData*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
NSData *jsonData = [[CJSONSerializer serializer] serializeArray:objectsArray error:nil];
return jsonData;
}
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return dataArray;
}
- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
NSDictionary *attributesByName = [[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 = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];
if ([[[description userInfo] objectForKey:@\"isExportable\"] boolValue] == YES) {
if (![description isToMany]) {
NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
if (relationshipObject) {
[valuesDictionary setObject:[self dataStructureFromManagedObject:relationshipObject] forKey:relationshipName];
}
continue;
}
NSSet *relationshipObjects = [managedObject valueForKey:relationshipName];
NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
for (NSManagedObject *relationshipObject in relationshipObjects) {
[relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
}
[valuesDictionary setObject:relationshipArray forKey:relationshipName];
}
}
return valuesDictionary;
}
I couldn\'t get the import working, maybe that has something to do with the fact that I\'m using Magical Record I\'m not sure, so Im just looping through the incoming JSON stream and creating objects manually...
回答8:
Marcus S. Zarra has inspired me to bring the recursive idea to a working version. In this version you don\'t need to set a key in CoreData and you can cut and paste it in your project :-)
// MARK: - encoding and decoding CoreData entity to dictionary
func dataStructureFromManagedObject( managedObject:NSManagedObject?, parentEntity: NSEntityDescription? = nil) -> NSMutableDictionary {
if (managedObject != nil) {
var attributesByName: NSDictionary = managedObject!.entity.attributesByName
var relationshipsByName: NSDictionary = managedObject!.entity.relationshipsByName
var valuesImmutableDictionary: NSDictionary = managedObject!.dictionaryWithValuesForKeys( attributesByName.allKeys)
var valuesDictionary: NSMutableDictionary = valuesImmutableDictionary.mutableCopy() as NSMutableDictionary
valuesDictionary.setObject( managedObject!.entity.name!, forKey: \"ManagedObjectName\")
for relationshipNameObject in relationshipsByName.allKeys {
var relationshipName: NSString = relationshipNameObject as NSString
var relationshipDescription: NSRelationshipDescription? = relationshipsByName.objectForKey( relationshipName) as? NSRelationshipDescription
if !relationshipDescription!.toMany {
// ono to one
if parentEntity == nil || (relationshipDescription! as NSRelationshipDescription).destinationEntity != parentEntity! {
// no parent or relationship is \"downward\" -> object for relationship must be added
var relationshipObject: NSManagedObject? = managedObject!.valueForKey( relationshipName) as? NSManagedObject
var relationshipObjectDictionary: NSMutableDictionary = self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity)
valuesDictionary.setObject( relationshipObjectDictionary, forKey: relationshipName)
} else {
// relationship is \"upward\" -> nothing to do
}
} else {
// one to many -> all objects must be added
var relationshipObjects: NSSet = managedObject!.mutableSetValueForKey( relationshipName)
var relationshipArray:NSMutableArray = []
for relationshipObjectRaw in relationshipObjects {
var relationshipObject:NSManagedObject? = relationshipObjectRaw as? NSManagedObject
if relationshipObject != nil && !relationshipObject!.entity.isKindOfEntity( managedObject!.entity) {
relationshipArray.addObject(self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity))
}
}
valuesDictionary.setObject( relationshipArray, forKey: relationshipName)
}
}
return valuesDictionary
} else {
return NSMutableDictionary()
}
}
func managedObjectFromStructure( structureDictionary: NSDictionary, moc: NSManagedObjectContext, parentObject: NSManagedObject? = nil) -> NSManagedObject {
if structureDictionary.count > 0 {
var objectName:NSString = structureDictionary.objectForKey( \"ManagedObjectName\") as NSString
var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
var relationshipsByName: NSDictionary = managedObject.entity.relationshipsByName
var realObjectStructure:NSMutableDictionary = structureDictionary.mutableCopy() as NSMutableDictionary
realObjectStructure.removeObjectForKey( \"ManagedObjectName\")
for key in realObjectStructure.allKeys {
// search for \"ManagedObjectName\" relationship entrys and delete them before filling the managedObject from this structure
for relationshipName in relationshipsByName.allKeys {
if relationshipName as NSString == key as NSString {
realObjectStructure.removeObjectForKey( key)
}
}
}
managedObject.setValuesForKeysWithDictionary( realObjectStructure)
// the main object with attributes is created. Now care about the relationships
for relationshipName in managedObject.entity.relationshipsByName.keys {
var description:NSRelationshipDescription = relationshipsByName.objectForKey( relationshipName) as NSRelationshipDescription
if !description.toMany {
// to one relationship
if parentObject == nil || description.destinationEntity != parentObject!.entity {
// no parent or relationship is \"downward\" -> recurse structure to add
var childStructureDictionary:NSDictionary = structureDictionary.objectForKey( relationshipName) as NSDictionary
if childStructureDictionary.count > 0 {
// dictionary not empty -> object must be created and added
var childObject:NSManagedObject? = self.managedObjectFromStructure( childStructureDictionary, moc: moc, parentObject: managedObject)
// validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println(\"Error: Object not in valid state for update!!! -> \\(error)\")
} else {
managedObject.setValue( childObject, forKey: relationshipName as NSString)
}
} else {
// relationship is \"upward\" -> nothing to do
}
}
} else {
// to many relationship
var relationshipSet:NSMutableSet = managedObject.mutableSetValueForKey( relationshipName as NSString)
var relationshipArray:NSArray = structureDictionary.objectForKey( relationshipName as NSString) as NSArray
for childStructureDictionary in relationshipArray {
if childStructureDictionary.count > 0 {
// dictionary not empty -> object must be created and added
var childObject:NSManagedObject = self.managedObjectFromStructure( childStructureDictionary as NSDictionary, moc: moc, parentObject: managedObject)
// validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println( \"Error: Object not in valid state for update!!! -> \\(error)\")
} else {
relationshipSet.addObject( childObject)
}
} else {
// no object was behind the relationship -> nothing to do
}
}
// save set
managedObject.setValue( relationshipSet, forKey: relationshipName as NSString)
}
}
// final check validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println( \"Error: Object not in valid state for update although all previous check are passed!!! -> \\(error)\")
}
return managedObject
} else {
println( \"Error: structure for object was empty. this should not happen at this point\")
var objectName:NSString = structureDictionary.objectForKey( \"ManagedObjectName\") as NSString
var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
return managedObject
}
}
func dataStructuresFromManagedObjects( managedObjects: NSArray) -> NSArray {
var dataArray:NSMutableArray = []
for managedObject in managedObjects {
dataArray.addObject( self.dataStructureFromManagedObject(managedObject as? NSManagedObject))
}
return dataArray
}
The key here is to pass the parent entity as argument to the recursion, so we can decide which relationship we have to fill with data. So the both functions: dataStructureFromManagedObject
and managedObjectFromStructure
can encode and decode any entity object from CoreData into a dictionary and back into an object.