JSON et Core Data sur iPhone

93

J'ai un graphique d'objet de données de base (composé de deux entités liées par une relation à plusieurs).

J'étais curieux, en tant que développeur iPhone relativement inexpérimenté, si quelqu'un pouvait recommander une approche et une implémentation JSON appropriée pour l'iPhone, ce qui me permettrait de:

  1. convertir les enregistrements de données de base en une chaîne JSON (tout en maintenant la relation entre les entités); et

  2. reconvertissez la chaîne JSON en objets de données de base (encore une fois en préservant la relation entre les entités).

J'ai recherché, sans succès, un exemple de tutoriel / code sur ce point afin que toute aide soit reçue avec gratitude.

Urizen
la source
3
Pour tous ceux qui recherchent cela pour iOS5, il y a maintenant NSJSONSerialization developer.apple.com/library/mac/#documentation/Foundation/... stackoverflow.com/questions/6726899/nsjsonserialization-in-ios5
nicerobot
Je sais que cette question est un peu ancienne, mais j'ai créé une bibliothèque simple appelée OSReflectionKit , qui vous permet de sérialiser / désérialiser des objets vers / depuis JSON, en utilisant NSJSONSerialization ou NSDictionary. Il prend également en charge les objets Core Data.
Alexandre OS

Réponses:

103

Tout d'abord, choisissez une bibliothèque JSON à utiliser, personnellement, j'aime TouchJSON, mais plusieurs autres sont également très bien. La partie compliquée, bien que pas très difficile, est de convertir vos objets gérés en structures appropriées pour la conversion. J'ai écrit ça très vite donc il peut y avoir une erreur ou deux :)

Les méthodes que vous appelez sont:

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

Et la mise en œuvre est la suivante:

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

Maintenant, c'est récursif, vous pouvez donc facilement traduire l'ensemble de votre magasin persistant si vous ne faites pas attention. Surveillez vos relations et assurez-vous qu'elles «descendent» uniquement dans l'arborescence des objets afin de ne récupérer que les objets que vous souhaitez traduire.

Marcus S. Zarra
la source
Merci encore pour une autre excellente réponse et pour votre livre très utile! :)
Urizen
2
Salut Marcus. Je viens d'essayer le code ci-dessus (avec quelques modifications mineures pour le faire compiler et l'exécution semble se poursuivre indéfiniment jusqu'à ce que l'application plante). Désolé de vous déranger, mais j'étais curieux de savoir si vous pouviez peut-être m'indiquer la bonne direction pour résoudre ce problème. Cela semble se produire avec la récursivité dans la méthode datastructureFromManagedObject ...
Urizen
1
Dépend de votre structure de données. Si votre modèle produit une boucle, il fonctionnera pour toujours. Examinez votre modèle de données et assurez-vous qu'il s'agit d'une conception d'arborescence ou placez des arrêts logiques dans le code récursif pour éviter la boucle.
Marcus S.Zarra
1
Avez-vous réellement essayé d'exécuter ce code? Il y a tellement d'erreurs. dataStructureForManagedObject n'existe même pas. Je pensais que c'était peut-être juste une faute de frappe, mais si vous le changez en dataStructureFromManagedObject, le tout rebondit à l'infini entre les paires de relations. Est-ce que je manque un code supplémentaire ici?
Chris Mitchelmore
1
Cet exemple de code a été écrit dans le navigateur il y a deux ans. Il était destiné à inspirer et non à copier-coller. En ce qui concerne une boucle infinie, cela signifie que vous avez une boucle dans votre modèle et que vous devrez ensuite ajouter une logique spécifique au modèle à votre application pour briser le cycle. Il existe plusieurs façons de le faire qui ne figurent pas dans cet exemple.
Marcus S.Zarra
12

Je voulais juste souligner une petite faute de frappe, qui a provoqué le blocage du code, et j'espère que cela vous fera gagner quelques minutes.

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

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

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

devrait vraiment être NSMutableArray *dataArray = [[NSMutableArray alloc] init];

c'est tout.

Merci

creativeKoder
la source
10

La synchronisation des données de base avec des rails est une présentation détaillée qui comprend un exemple de code pour sérialiser / désérialiser vos objets Core Data vers / depuis JSON (passez à la diapositive 55 pour la partie Core Data). Son exemple de code suppose un modèle assez simple sans relations, même si je pense qu'il serait assez facile à étendre.

La présentation donne également des détails sur la synchronisation de votre modèle Core Data avec une application Web basée sur REST, avec des pointeurs vers certaines bibliothèques utiles, notamment ObjectiveResource et ASIHTTPRequest . Je ne sais pas si c'est ce que vous essayez de faire, mais cela vaut même le coup d'œil pour le code Core Data.

Christopher Pickslay
la source
7

Si vous avez un NSDatedans votre objet géré, comme mentionné ci-dessus dans l'un des commentaires, vous aurez des problèmes pour sérialiser l'objet contenant le NSDate. Une solution simple consiste à ajouter une JSONDataRepresentationméthode àNSDate utiliser les catégories objective-c.

Ajoutez ces deux fichiers à votre projet:

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
Joshaidan
la source
2

Je suis tombé sur ce post qui fonctionne très bien.

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

Comme c'est récursif, les relations plusieurs-à-plusieurs vont continuer à se boucler. Pour éviter cela, j'ai ajouté une clé "isExportable" au dictionnaire d'informations utilisateur des relations dans mon modèle Core Data. Vous pouvez ensuite vérifier cette clé et choisir de ne pas parcourir les relations sans elle.

entrez la description de l'image ici

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];
            }
        }
    }
}
Brandon Schlenker
la source
2

Je pensais juste publier une mise à jour rapide de cette question. J'ai suivi les réponses de Marcus et Brandon et j'ai proposé ceci pour l'exportation JSON (il utilise toujours 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;
}

Je ne pouvais pas faire fonctionner l'importation, peut-être que cela a quelque chose à voir avec le fait que j'utilise Magical Record, je ne suis pas sûr, donc je fais une boucle sur le flux JSON entrant et crée des objets manuellement ...

Carl Taylor
la source
1

Marcus S. Zarra m'a inspiré pour amener l'idée récursive à une version de travail. Dans cette version, vous n'avez pas besoin de définir une clé dans CoreData et vous pouvez la couper et la coller dans votre projet :-)

// 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 clé ici est de transmettre l'entité parente comme argument à la récursivité, afin que nous puissions décider quelle relation nous devons remplir avec des données. Ainsi, les deux fonctions: dataStructureFromManagedObjectet managedObjectFromStructurepeuvent encoder et décoder n'importe quel objet d'entité de CoreData dans un dictionnaire et de nouveau dans un objet.

MPajak
la source