JSON y Core Data en el iPhone

Tengo un gráfico de objeto de datos básicos (que consta de dos entidades vinculadas por una relación a-muchos).

Tenía curiosidad, como un desarrollador de iPhone relativamente inexperto, si alguien podría recomendar un enfoque, y una implementación JSON adecuada para el iPhone, que me permitiría:

  1. convertir los registros de datos centrales en una cadena JSON (manteniendo la relación entre las entidades); y

  2. convierta la cadena JSON nuevamente en objetos de datos centrales (conservando nuevamente la relación entre las entidades).

He buscado, sin éxito, una muestra de tutorial / código sobre este punto para que cualquier ayuda se reciba con gratitud.

Primero, elija una biblioteca JSON para usar, personalmente me gusta TouchJSON pero también hay otros que son bastante amables. La parte complicada, aunque no es muy difícil, es convertir sus objetos gestionados en estructuras adecuadas para la conversión. Escribí esto muy rápido así que puede tener un error o dos 🙂

Los métodos que llamas son:

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

Y la implementación es la siguiente:

 - (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]; } 

Ahora bien, esto es recursivo, por lo que puede terminar traduciendo fácilmente toda su tienda persistente si no tiene cuidado. Observe sus relaciones y asegúrese de que solo “bajen” el árbol de objetos para que solo obtenga los objetos que desea traducir.

Solo quería señalar un pequeño error tipográfico que hizo que el código fallara y, con suerte, esto le ahorrará unos minutos.

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

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

realmente debería ser NSMutableArray *dataArray = [[NSMutableArray alloc] init];

eso es todo.

gracias

La sincronización de datos principales con Rails es una presentación detallada que incluye un código de muestra para serializar / deserializar sus objetos de datos centrales a / desde JSON (pase a la diapositiva 55 para la parte de datos principales). Su código de muestra supone un modelo bastante simple sin relaciones, aunque creo que sería bastante fácil de extender.

La presentación también entra en detalles sobre cómo mantener sincronizado el modelo de Core Data con una aplicación web basada en REST, con punteros a algunas bibliotecas útiles, como ObjectiveResource y ASIHTTPRequest . No estoy seguro de si eso es lo que intenta hacer, pero vale la pena ver incluso el código de Core Data.

Si tiene un NSDate en su objeto administrado, como se mencionó anteriormente en uno de los comentarios, tendrá problemas para serializar el objeto que contiene el NSDate . Una solución simple es agregar un método NSDate a NSDate usando categorías objective-c.

Agregue estos dos archivos a su proyecto:

NSdate.h:

 #import  @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 

Hay una lib que hace la sincronización JSON para usted: https://github.com/sixdegrees/lidenbrock

Me encontré con esta publicación que funciona muy bien.

http://touchalicious.com/blog/2009/10/25/turn-core-data-models-into-json.html

Dado que esto es recursivo, las relaciones de muchos a muchos van a continuar recorriéndose. Para evitar esto, agregué una clave “isExportable” al diccionario de información de usuario de las relaciones en mi modelo de Datos Core. Luego puede verificar esta clave y elegir no recorrer las relaciones sin ella.

enter image description here

 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]; } } } } 

Solo pensé en ingresar una actualización rápida a esta pregunta. Seguí las respuestas de Marcus y Brandon y se me ocurrió esto para la exportación de JSON (todavía usa TouchJSON):

 - (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; } 

No pude hacer que la importación funcionara, tal vez eso tiene algo que ver con el hecho de que estoy usando Magical Record. No estoy seguro, así que estoy simplemente recorriendo la stream JSON entrante y creando objetos manualmente …

Marcus S. Zarra me ha inspirado para llevar la idea recursiva a una versión funcional. En esta versión, no necesita establecer una clave en CoreData y puede cortarla y pegarla en su proyecto 🙂

 // 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 } 

La clave aquí es pasar la entidad padre como argumento a la recursión, para que podamos decidir qué relación tenemos que completar con los datos. Así que ambas funciones: dataStructureFromManagedObject y managedObjectFromStructure pueden codificar y decodificar cualquier objeto de entidad desde CoreData en un diccionario y volver a un objeto.