JSON i podstawowe dane na iPhonie


93

Mam podstawowy wykres obiektów danych (składający się z dwóch jednostek połączonych relacją wiele).

Byłem ciekawy, jako stosunkowo niedoświadczony programista iPhone'a, czy ktokolwiek mógłby polecić podejście i odpowiednią implementację JSON dla iPhone'a, która pozwoliłaby mi:

  1. przekonwertować podstawowe rekordy danych na ciąg JSON (przy zachowaniu relacji między jednostkami); i

  2. przekonwertuj ciąg JSON z powrotem na podstawowe obiekty danych (ponownie zachowując relację między jednostkami).

Bezskutecznie szukałem samouczka / przykładowego kodu na ten temat, więc każda pomoc byłaby wdzięczna.



Wiem, że to pytanie jest trochę stare, ale stworzyłem prostą bibliotekę o nazwie OSReflectionKit , która umożliwia serializację / deserializację obiektów do / z JSON za pomocą NSJSONSerialization lub NSDictionary. Obsługuje również obiekty Core Data.
Alexandre OS

Odpowiedzi:


103

Najpierw wybierz bibliotekę JSON do użycia, osobiście lubię TouchJSON, ale kilka innych jest również całkiem fajnych. Część skomplikowana, choć niezbyt trudna, polega na przekonwertowaniu zarządzanych obiektów na struktury odpowiednie do konwersji. Napisałem to naprawdę szybko, więc może mieć błąd lub dwa :)

Metody, które wywołujesz, to:

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

A realizacja wygląda następująco:

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

Teraz jest to rekurencyjne, więc jeśli nie będziesz ostrożny, możesz łatwo skończyć z tłumaczeniem całego trwałego magazynu. Obserwuj swoje relacje i upewnij się, że przechodzą one tylko „w dół” drzewa obiektów, aby uzyskać tłumaczenie tylko tych obiektów, które chcesz przetłumaczyć.


Jeszcze raz dziękuję za kolejną doskonałą odpowiedź i za bardzo pomocną książkę! :)
Urizen

2
Cześć Marcus. Właśnie wypróbowałem powyższy kod (z kilkoma drobnymi poprawkami, aby go skompilować, a wykonywanie wydaje się trwać w nieskończoność, aż do awarii aplikacji). Przepraszam, że przeszkadzam, ale byłem ciekawy, czy możesz wskazać mi właściwy kierunek rozwiązania tego problemu. Wydaje się, że dzieje się tak z rekurencją w metodzie datastructureFromManagedObject ...
Urizen

1
Zależy od struktury danych. Jeśli twój model utworzy pętlę, będzie działał wiecznie. Przejrzyj model danych i upewnij się, że jest to projekt drzewa, lub zatrzymaj logikę w kodzie rekurencyjnym, aby zapobiec zapętleniu.
Marcus S. Zarra

1
Czy faktycznie próbowałeś uruchomić ten kod? Jest tak wiele błędów. dataStructureForManagedObject nawet nie istnieje. Pomyślałem, że to może być po prostu literówka, ale jeśli zmienisz to na dataStructureFromManagedObject, całość po prostu zapętla się w nieskończoność odbijając się między parami relacji. Czy brakuje mi tu dodatkowego kodu?
Chris Mitchelmore

1
Ten przykład kodu został napisany w przeglądarce dwa lata temu. Miał on inspirować, a nie kopiować i wklejać. Jeśli chodzi o nieskończoną pętlę, oznacza to, że masz pętlę w modelu i musisz dodać do aplikacji logikę specyficzną dla modelu, aby przerwać cykl. Jest na to kilka sposobów, których nie ma w tym przykładzie.
Marcus S. Zarra

12

Chciałem tylko wskazać małą literówkę, która spowodowała awarię kodu i mam nadzieję, że zaoszczędzi ci to kilka minut.

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

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

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

naprawdę powinno być NSMutableArray *dataArray = [[NSMutableArray alloc] init];

to wszystko.

Dziękuję Ci


10

Synchronizacja danych podstawowych z Railsami to szczegółowa prezentacja, która zawiera przykładowy kod do serializacji / deserializacji obiektów danych podstawowych do / z formatu JSON (przejdź do slajdu 55 w części dotyczącej danych podstawowych). Jego przykładowy kod zakłada dość prosty model bez relacji, chociaż myślę, że byłoby całkiem łatwe do rozszerzenia.

Prezentacja zawiera również szczegółowe informacje na temat synchronizacji modelu Core Data z aplikacją internetową opartą na REST, ze wskazówkami do niektórych przydatnych bibliotek, w tym ObjectiveResource i ASIHTTPRequest . Nie jestem pewien, czy to jest to, co próbujesz zrobić, ale warto przyjrzeć się nawet kodowi Core Data.


7

Jeśli masz NSDatew zarządzanym obiekcie, jak wspomniano powyżej w jednym z komentarzy, będziesz mieć problemy z serializacją obiektu zawierającego NSDate. Prostą poprawką jest dodanie JSONDataRepresentationmetody doNSDate korzystania z kategorii celu-c.

Dodaj te dwa pliki do swojego projektu:

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

3

Pomyślałem, że id opublikuje szybką aktualizację tego pytania. Postępowałem zgodnie z odpowiedziami Marcusa i Brandona i wymyśliłem to do eksportu JSON (nadal używa 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;
}

Nie udało mi się uruchomić importu, może to ma coś wspólnego z faktem, że używam Magical Record, nie jestem pewien, więc po prostu przeglądam przychodzący strumień JSON i ręcznie tworzę obiekty ...



2

Trafiłem na ten post, który działa bardzo dobrze.

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

Ponieważ jest to rekurencyjne, relacje „wiele do wielu” będą się zapętlać. Aby tego uniknąć, dodałem klucz „isExportable” do słownika informacji o użytkowniku relacji w moim modelu Core Data. Następnie możesz sprawdzić ten klucz i zdecydować się nie zapętlać relacji bez niego.

wprowadź opis obrazu tutaj

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

1

Marcus S. Zarra zainspirował mnie do przeniesienia idei rekurencyjnej do działającej wersji. W tej wersji nie musisz ustawiać klucza w CoreData i możesz go wyciąć i wkleić w swoim projekcie :-)

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

Kluczem jest tutaj przekazanie jednostki nadrzędnej jako argumentu do rekursji, abyśmy mogli zdecydować, którą relację musimy wypełnić danymi. Tak więc obie funkcje: dataStructureFromManagedObjecti managedObjectFromStructuremogą kodować i dekodować dowolny obiekt jednostki z CoreData do słownika iz powrotem do obiektu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.