2010-03-02 2 views
90

У меня есть базовый граф объектов данных (состоящий из двух объектов, связанных отношением to-many).JSON и основные данные на iPhone

мне было интересно, как относительно неопытный разработчик iPhone, может ли кто-нибудь рекомендовать подход, и подходящую реализацию JSON для iPhone, который позволил бы мне:

  1. конвертировать записи основных данных, в строка JSON (сохраняя связь между сущностями); и

  2. преобразовать строку JSON обратно в основные объекты данных (снова сохраняя связь между объектами).

Я искал, безуспешно, для примера учебника/кода на этом этапе, поэтому любая помощь была бы с благодарностью принята.

+3

Для тех, кто интересуется этим для iOS5, теперь существует 'NSJSONSerialization' http://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html http: // stackoverflow. com/questions/6726899/nsjsonserialization-in-ios5 – nicerobot

+0

Я знаю, что этот вопрос немного стар, но я создал простую библиотеку под названием [OSReflectionKit] (https://github.com/iAOS/OSReflectionKit), что позволяет вы должны сериализовать/десериализовать объекты в/из JSON, используя [NSJSONSerialization] (http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html) или NSDictionary. Он также поддерживает объекты Core Data. –

ответ

101

Во-первых, выберите библиотеку JSON для использования, мне лично нравится TouchJSON, но некоторые другие тоже неплохие. Сложная часть, хотя и не очень сложная, заключается в преобразовании управляемых объектов в подходящие структуры для преобразования. Я написал это очень быстро, так что, возможно, есть ошибка или два :)

Методы вы называете, являются:

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

И реализация выглядит следующим образом:

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

Теперь это рекурсивное так что вы можете легко завершить перевод своего постоянного хранилища, если не будете осторожны. Следите за своими отношениями и убедитесь, что они только «опускают» дерево объектов, так что вы получаете только те объекты, которые хотите перевести.

+0

Еще раз спасибо за отличный ответ и за вашу очень полезную книгу! :) – Urizen

+2

Привет, Маркус. Я только что попробовал код выше (с некоторыми незначительными изменениями, чтобы скомпилировать его, и выполнение, похоже, продолжается бесконечно, пока приложение не сработает). Извините, что беспокою вас, но мне было любопытно, если бы вы могли указать мне в правильном направлении для решения этой проблемы. Это похоже на рекурсию в методе datastructureFromManagedObject ... – Urizen

+1

Зависит от вашей структуры данных. Если ваша модель создаст цикл, она будет работать вечно.Просмотрите свою модель данных и убедитесь, что это дизайн дерева или поместите логические остановки в рекурсивный код, чтобы предотвратить цикл. –

9

Synchronizing Core Data with Rails - подробная презентация, которая включает пример кода для сериализации/десериализации объектов Core Data в/из JSON (пропустите слайд 55 для части основных данных). Его пример кода предполагает довольно простую модель без отношений, хотя я думаю, что ее было бы довольно легко расширить.

В презентации также подробно описывается сохранение вашей модели Core Data в синхронизации с веб-приложением на основе REST с указателями на некоторые полезные библиотеки, включая ObjectiveResource и ASIHTTPRequest. Не уверен, что это то, что вы пытаетесь сделать, но это стоит посмотреть даже на код Core Data.

11

Я просто хотел указать небольшую опечатку, которая вызвала сбой кода, и, надеюсь, это сэкономит вам несколько минут.

- (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 = [[NSMutableArray alloc] init];

это все.

спасибо

+1

Я отредактировал это исправление в вопросе. – pkamb

6

Если у вас есть в вашем управляемом объекте в NSDate, как уже упоминалось выше, в одном из комментариев, вы будете иметь проблемы сериализации объект, содержащий NSDate. Простое исправление заключается в добавлении метода JSONDataRepresentation к NSDate с использованием объектных категорий.

Добавьте эти два файла в проект:

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

Существует В Lib, что делает JSON синхронизация для вас: https://github.com/sixdegrees/lidenbrock

+4

Проект больше не указан по этой ссылке на 30 июля 2012 года – Nick

+0

Ссылка недействительна .. – Magurizio

2

Я наткнулся на этот пост, который работает очень хорошо.

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

Поскольку это рекурсивный, многие-ко-многим собираются держать перекручивание через себя. Чтобы этого избежать, я добавил ключ «isExportable» в словарь пользовательской информации о взаимоотношениях в моей модели Core Data. Затем вы можете проверить этот ключ и выбрать, чтобы он не пересекал отношения без него.

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

Просто думал, что идентификатор отправить быстрое обновление на этот вопрос. Я последовал за ответы от Маркуса и Брэндон и придумал это для JSON экспорта (он использует 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; 
} 

я не мог получить работу импорта, возможно, что-то делать с тем, что я Я использую магическую запись. Я не уверен, поэтому я просто перебираю входящий поток JSON и создаю объекты вручную ...

1

Marcus S. Zarra вдохновил меня привести рекурсивную идею к рабочей версии. В этой версии вам не нужно, чтобы установить ключ в CoreData и вы можете вырезать и вставить его в свой проект :-)

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

Ключевым моментом здесь является передать родительский объект в качестве аргумента в рекурсии, так мы можем решить, какие отношения мы должны заполнить данными. Таким образом, обе функции: dataStructureFromManagedObject и managedObjectFromStructure могут кодировать и декодировать любой объект объекта из CoreData в словарь и обратно в объект.

+0

Этот код является датой и имеет массу проблем. – HotFudgeSunday

Смежные вопросы