JSON e Core Data su iPhone

Ho un grafico di oggetti dati di base (composto da due quadro collegate da una relazione to-many).

Ero curioso, in quanto sviluppatore di iPhone relativamente inesperto, se qualcuno potesse consigliare un approccio e un’implementazione JSON adatta per l’iPhone, il che mi permettesse di:

  1. convertire i record di dati di base in una stringa JSON (mantenendo la relazione tra le entity framework); e

  2. convertire la stringa JSON in oggetti dati di base (mantenendo di nuovo la relazione tra quadro).

Ho cercato, senza successo, per un tutorial / codice di esempio su questo punto, quindi qualsiasi aiuto sarebbe stato accolto con gratitudine.

Per prima cosa, scegli una libreria JSON da usare, personalmente mi piace TouchJSON, ma molti altri là fuori sono abbastanza carini. La parte complicata, sebbene non molto difficile, consiste nel convertire gli oggetti gestiti in strutture adeguate per la conversione. Ho scritto questo vero veloce in modo che possa avere un errore o due 🙂

I metodi che chiami sono:

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

E l’implementazione è la seguente:

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

Ora questo è ricorsivo in modo che tu possa facilmente finire per tradurre il tuo intero negozio persistente se non stai attento. Osserva le tue relazioni e assicurati di “abbassare” solo l’albero degli oggetti in modo da ottenere solo gli oggetti che vuoi tradurre.

Volevo solo segnalare un piccolo errore di battitura, che ha causato l’arresto anomalo del codice, e spero che questo ti consenta di risparmiare qualche minuto.

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

dovrebbe essere veramente NSMutableArray *dataArray = [[NSMutableArray alloc] init];

questo è tutto.

grazie

La sincronizzazione dei dati principali con Rails è una presentazione dettagliata che include un codice di esempio per serializzare / deserializzare gli oggetti di dati di base da / a JSON (passare alla diapositiva 55 per la parte Core Data). Il suo codice di esempio assume un modello abbastanza semplice senza relazioni, anche se penso che sarebbe piuttosto facile estenderlo.

La presentazione contiene anche alcuni dettagli su come mantenere il tuo modello Core Data in sincronia con un’applicazione web basata su REST, con riferimenti ad alcune utili librerie, tra cui ObjectiveResource e ASIHTTPRequest . Non sono sicuro che sia quello che stai cercando di fare, ma vale la pena dare un’occhiata anche al codice di Core Data.

Se si dispone di un NSDate nell’object gestito, come menzionato sopra in uno dei commenti, si avranno problemi con la serializzazione dell’object contenente l’ NSDate . Una semplice soluzione consiste nell’aggiungere un metodo NSDate a NSDate utilizzando le categorie objective-c.

Aggiungi questi due file al tuo progetto:

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 

C’è una lib che fa la sincronizzazione JSON per te: https://github.com/sixdegrees/lidenbrock

Mi sono imbattuto in questo post che funziona molto bene.

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

Dato che questo è ricorsivo, le relazioni molti-a-molti continueranno a ripetersi. Per evitare ciò, ho aggiunto una chiave “isExportable” al dizionario delle informazioni utente delle relazioni nel mio modello Core Data. È quindi ansible verificare la presenza di questa chiave e scegliere di non eseguire il loop delle relazioni senza di essa.

inserisci la descrizione dell'immagine qui

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

Ho solo pensato di postare un rapido aggiornamento a questa domanda. Ho seguito le risposte di Marcus e Brandon e ne ho ricavato l’esportazione per JSON (utilizza ancora 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; } 

Non sono riuscito a far funzionare l’importazione, forse questo ha a che fare con il fatto che sto usando Magical Record, non ne sono sicuro, quindi sto semplicemente scorrendo il stream JSON in arrivo e creando oggetti manualmente …

Marcus S. Zarra mi ha ispirato a portare l’idea ricorsiva a una versione funzionante. In questa versione non hai bisogno di impostare una chiave in CoreData e puoi tagliarla e incollarla nel tuo progetto 🙂

 // 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 chiave qui è passare l’entity framework genitore come argomento alla ricorsione, così possiamo decidere quale relazione dobbiamo riempire con i dati. Quindi entrambe le funzioni: dataStructureFromManagedObject e managedObjectFromStructure possono codificare e decodificare qualsiasi object quadro da CoreData in un dizionario e tornare in un object.