JSON und Core Data auf dem iPhone


93

Ich habe einen Kerndatenobjektgraphen (bestehend aus zwei Entitäten, die durch eine zu viele Beziehung verbunden sind).

Als relativ unerfahrener iPhone-Entwickler war ich neugierig, ob jemand einen Ansatz und eine geeignete JSON-Implementierung für das iPhone empfehlen könnte, die es mir ermöglichen würde:

  1. Konvertieren der Kerndatensätze in eine JSON-Zeichenfolge (unter Beibehaltung der Beziehung zwischen den Entitäten); und

  2. Konvertieren Sie die JSON-Zeichenfolge zurück in Kerndatenobjekte (wobei die Beziehung zwischen Entitäten beibehalten wird).

Ich habe erfolglos nach einem Tutorial / Codebeispiel zu diesem Punkt gesucht, damit jede Unterstützung dankbar erhalten wird.



Ich weiß, dass diese Frage etwas alt ist, aber ich habe eine einfache Bibliothek namens OSReflectionKit erstellt , mit der Sie Objekte mithilfe von NSJSONSerialization oder NSDictionary zu / von JSON serialisieren / deserialisieren können . Es werden auch Core Data-Objekte unterstützt.
Alexandre OS

Antworten:


103

Wählen Sie zunächst eine JSON-Bibliothek aus, die ich verwenden möchte. Ich persönlich mag TouchJSON, aber einige andere sind auch sehr nett. Der komplizierte Teil ist zwar nicht sehr schwierig, besteht jedoch darin, Ihre verwalteten Objekte in geeignete Strukturen für die Konvertierung zu konvertieren. Ich habe das ganz schnell geschrieben, damit es ein oder zwei Fehler gibt :)

Die Methoden, die Sie aufrufen, sind:

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

Und die Implementierung ist wie folgt:

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

Dies ist jetzt rekursiv, sodass Sie leicht Ihren gesamten persistenten Speicher übersetzen können, wenn Sie nicht vorsichtig sind. Beobachten Sie Ihre Beziehungen und stellen Sie sicher, dass sie nur den Objektbaum "runter" gehen, damit Sie nur die Objekte erhalten, die Sie übersetzen möchten.


Nochmals vielen Dank für eine weitere hervorragende Antwort und für Ihr sehr hilfreiches Buch! :)
Urizen

2
Hallo Marcus. Ich habe gerade den obigen Code ausprobiert (mit einigen geringfügigen Änderungen, damit er kompiliert werden kann, und die Ausführung scheint unbegrenzt zu dauern, bis die App abstürzt). Es tut mir leid, Sie zu stören, aber ich war neugierig, ob Sie mich vielleicht in die richtige Richtung zur Lösung dieses Problems weisen könnten. Es scheint mit der Rekursion in der datastructureFromManagedObject-Methode zu passieren ...
Urizen

1
Hängt von Ihrer Datenstruktur ab. Wenn Ihr Modell eine Schleife erzeugt, läuft es für immer. Überprüfen Sie Ihr Datenmodell und stellen Sie entweder sicher, dass es sich um ein Baumdesign handelt, oder fügen Sie logische Stopps in den rekursiven Code ein, um Schleifen zu vermeiden.
Marcus S. Zarra

1
Haben Sie tatsächlich versucht, diesen Code auszuführen? Es gibt so viele Fehler. dataStructureForManagedObject existiert nicht einmal. Ich dachte, es könnte nur ein Tippfehler sein, aber wenn Sie ihn in dataStructureFromManagedObject ändern, wird das Ganze nur unendlich zwischen Beziehungspaaren zurückgeworfen. Vermisse ich hier einen zusätzlichen Code?
Chris Mitchelmore

1
Dieses Codebeispiel wurde vor zwei Jahren im Browser geschrieben. Es sollte nicht zum Kopieren und Einfügen inspirieren. Bei einer Endlosschleife bedeutet dies, dass Sie eine Schleife in Ihrem Modell haben und dann Ihrer App modellspezifische Logik hinzufügen müssen, um den Zyklus zu unterbrechen. Es gibt verschiedene Möglichkeiten, dies in diesem Beispiel nicht zu tun.
Marcus S. Zarra

12

Ich wollte nur auf einen kleinen Tippfehler hinweisen, der zum Absturz des Codes führte, und hoffentlich sparen Sie dadurch ein paar Minuten.

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

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

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

sollte wirklich sein NSMutableArray *dataArray = [[NSMutableArray alloc] init];

das ist alles.

Danke


10

Das Synchronisieren von Core-Daten mit Rails ist eine detaillierte Präsentation, die Beispielcode zum Serialisieren / Deserialisieren Ihrer Core-Data-Objekte zu / von JSON enthält (fahren Sie mit Folie 55 für den Core-Data-Teil fort). Sein Beispielcode geht von einem ziemlich einfachen Modell ohne Beziehungen aus, obwohl ich denke, dass es ziemlich einfach zu erweitern wäre.

In der Präsentation wird auch detailliert beschrieben, wie Sie Ihr Core Data-Modell mit einer REST-basierten Webanwendung synchronisieren und auf einige nützliche Bibliotheken verweisen können , darunter ObjectiveResource und ASIHTTPRequest . Ich bin mir nicht sicher, ob Sie das versuchen, aber es lohnt sich, auch nach dem Core Data-Code zu suchen.


7

Wenn Sie ein NSDatein Ihrem verwalteten Objekt haben, wie oben in einem der Kommentare erwähnt, haben Sie Probleme beim Serialisieren des Objekts, das das enthält NSDate. Eine einfache Lösung besteht darin, eine JSONDataRepresentationMethode zur NSDateVerwendung von Ziel-C-Kategorien hinzuzufügen .

Fügen Sie Ihrem Projekt diese beiden Dateien hinzu:

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


2

Ich bin auf diesen Beitrag gestoßen, der sehr gut funktioniert.

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

Da dies rekursiv ist, werden sich viele-zu-viele-Beziehungen immer wieder selbst durchlaufen. Um dies zu vermeiden, habe ich dem Benutzerinformationswörterbuch der Beziehungen in meinem Kerndatenmodell einen "isExportable" -Schlüssel hinzugefügt. Sie können dann nach diesem Schlüssel suchen und festlegen, dass Beziehungen ohne ihn nicht durchlaufen werden.

Geben Sie hier die Bildbeschreibung ein

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

2

Ich dachte nur, ich poste ein schnelles Update zu dieser Frage. Ich folgte den Antworten von Marcus und Brandon und fand diese für den JSON-Export (es wird immer noch TouchJSON verwendet):

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

Ich konnte den Import nicht zum Laufen bringen, vielleicht hat das etwas damit zu tun, dass ich Magical Record verwende. Ich bin mir nicht sicher, also durchlaufe ich einfach den eingehenden JSON-Stream und erstelle Objekte manuell ...


1

Marcus S. Zarra hat mich inspiriert, die rekursive Idee in eine funktionierende Version zu bringen. In dieser Version müssen Sie keinen Schlüssel in CoreData festlegen und können ihn ausschneiden und in Ihr Projekt einfügen :-)

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

Der Schlüssel hier ist, die übergeordnete Entität als Argument an die Rekursion zu übergeben, damit wir entscheiden können, welche Beziehung wir mit Daten füllen müssen. Also die beiden Funktionen: dataStructureFromManagedObjectund managedObjectFromStructurekönnen jedes Entitätsobjekt von CoreData in ein Wörterbuch und zurück in ein Objekt codieren und decodieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.