【问题标题】:JSON and Core Data on the iPhoneiPhone 上的 JSON 和核心数据
【发布时间】:2011-01-22 16:04:15
【问题描述】:

我有一个核心数据对象图(由通过一对多关系链接的两个实体组成)。

作为一个相对缺乏经验的 iPhone 开发人员,我很好奇是否有人可以推荐一种方法,以及适用于 iPhone 的 JSON 实现,这将使我能够:

  1. 将核心数据记录转换为 JSON 字符串(同时保持实体之间的关系);和

  2. 将 JSON 字符串转换回核心数据对象(再次保留实体之间的关系)。

我已经搜索了关于这一点的教程/代码示例,但未成功,因此我们将不胜感激。

【问题讨论】:

标签: iphone objective-c json core-data


【解决方案1】:

首先,选择一个 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];
}

现在这是递归的,所以如果你不小心的话,你可以很容易地翻译整个持久存储。注意您的关系并确保它们只“向下”移动对象树,以便您只获得要翻译的对象。

【讨论】:

  • 再次感谢您提供另一个出色的答案和非常有用的书! :)
  • 嗨,马库斯。我刚刚尝试了上面的代码(进行了一些小的修改以使其编译并且执行似乎无限期地继续,直到应用程序崩溃)。很抱歉打扰你,但我很好奇你是否能指出我解决这个问题的正确方向。它似乎发生在 datastructureFromManagedObject 方法中的递归......
  • 取决于你的数据结构。如果您的模型将产生一个循环,那么它将永远运行。检查您的数据模型并确保它是树形设计或在递归代码中放置逻辑停止以防止循环。
  • 你真的试过运行这段代码吗?有很多错误。 dataStructureForManagedObject 甚至不存在。我认为这可能只是一个错字,但如果您将其更改为 dataStructureFromManagedObject ,那么整个事情只会在关系对之间无限循环。我在这里遗漏了一些额外的代码吗?
  • 这个代码示例是两年前在浏览器中编写的。它的目的是激发灵感,而不是复制和粘贴。至于无限循环,这意味着您的模型中有一个循环,然后您需要将模型特定的逻辑添加到您的应用程序以打破循环。有几种方法没有在这个例子中。
【解决方案2】:

我只是想指出一个导致代码崩溃的小错字,希望这可以为您节省几分钟。

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

仅此而已。

谢谢

【讨论】:

    【解决方案3】:

    Synchronizing Core Data with Rails 是一个详细的演示文稿,其中包括将 Core Data 对象序列化/反序列化到 JSON 的示例代码(跳到幻灯片 55 了解 Core Data 部分)。他的示例代码假设了一个没有关系的相当简单的模型,尽管我认为它很容易扩展。

    该演示文稿还详细介绍了如何使您的 Core Data 模型与基于 REST 的 Web 应用程序保持同步,并提供了一些有用的库的指针,包括 ObjectiveResourceASIHTTPRequest。不确定这是否是您想要做的,但即使是核心数据代码也值得一看。

    【讨论】:

      【解决方案4】:

      如果您的托管对象中有NSDate,如上面在其中一个cmets 中所述,那么序列化包含NSDate 的对象时会遇到问题。一个简单的解决方法是使用objective-c 类别将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
      

      【讨论】:

        【解决方案5】:

        只是想 id 发布一个快速更新这个问题。我按照 Marcus 和 Brandon 的回答提出了这个用于 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;
        }
        

        我无法让导入工作,也许这与我不确定我正在使用 Magical Record 的事实有关,所以我只是循环通过传入的 JSON 流并手动创建对象......

        【讨论】:

          【解决方案6】:

          有一个库可以为您进行 JSON 同步:https://github.com/sixdegrees/lidenbrock

          【讨论】:

          • 自 2012 年 7 月 30 日起,项目不再位于该链接
          【解决方案7】:

          我看到了这篇文章,效果很好。

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

          由于这是递归的,多对多关系将不断循环。为了避免这种情况,我在我的核心数据模型中的关系的用户信息字典中添加了一个“isExportable”键。然后,您可以检查此键并选择在没有它的情况下不循环关系。

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

          【讨论】:

            【解决方案8】:

            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
            }
            

            这里的关键是将父实体作为参数传递给递归,这样我们就可以决定我们必须用数据填充哪个关系。所以这两个函数:dataStructureFromManagedObjectmanagedObjectFromStructure 可以将 CoreData 中的任何实体对象编码和解码到字典中,然后再返回到对象中。

            【讨论】:

              猜你喜欢
              • 2010-11-04
              • 1970-01-01
              • 2011-08-21
              • 2011-05-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-03-10
              相关资源
              最近更新 更多