使用 RSAPI 将 JSON 保存到 Core Data

Posted

技术标签:

【中文标题】使用 RSAPI 将 JSON 保存到 Core Data【英文标题】:Using RSAPI to save JSON into Core Data 【发布时间】:2012-09-04 13:46:21 【问题描述】:

我正在尝试将 JSON 从 Web 服务解析为 Core Data。我查看了 RSAPI 库,但我真的不明白如何在我的情况下使用它。到目前为止,我用来获取 JSON 数据的方法是:json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; NSData *data = [NSData dataWithContentsOfURL:url];,然后在我的 tableview 中将其显示为数组。关于如何将 JSON 数据保存到我的自定义实体的核心数据中的任何建议?

【问题讨论】:

【参考方案1】:

简单/危险的方式

Cocoa 提供了一种方法,使这变得非常简单。感谢键值编码,在 NSObject 中有一个方法可以将上述内容简化为一行:

[myObj setValuesForKeysWithDictionary:jsonDict];

这将遍历 jsonDict 中的键/值对,并在 myObj 上设置相同的键/值对。唯一需要注意的是键名必须匹配,但假设您在数据模型中将它们命名为相同,这应该不是问题。

实际上还有一个警告,这是一个大问题。在此方法中,字典中包含的键确定在托管对象上使用哪些键。但是如果字典包含一个不属于实体描述的键怎么办?然后,您的应用程序崩溃,并显示有关对象如何不符合键的键值编码的错误。在此处使用此方法会使您的应用程序的稳定性受制于您正在使用的 Web 服务。如果您对 Web 服务没有绝对控制权,那么在此处使用此方法将面临严重风险。我们可以做得更好。

检查托管对象

由于我们使用的是 Core Data,我们可以使用 NSEntityDescription 来检查托管对象的属性并扭转上述逻辑。我们可以使用托管对象,而不是使用传入的字典来确定要使用的键/值对。

您可以通过询问其实体来查找托管对象的实体描述。这会给你一个 NSEntityDescription ,然后你可以询问各种有用的信息,比如存在哪些属性。从 JSON 转换时,这会导致更安全的方法:

NSDictionary *attributes = [[myObj entity] attributesByName];
for (NSString *attribute in attributes) 
id value = [jsonDict objectForKey:attribute];
if (value == nil) 
    continue;

[myObj setValue:value forKey:attribute];

此代码遍历实体的属性,在字典中为每个属性查找一个键,并将该键/值对应用于托管对象。这仍然很好且通用,并且具有对传入数据的更改不会再使应用程序崩溃的优点。它仅在字典中查找实体描述中实际存在的属性的值。额外的字典键会被忽略。

检查 nil 是因为代码确实命中了实体的所有属性。如果实体具有传入数据中不存在的任何属性(例如,“收藏夹”标志),则代码最终会为该属性设置 nil 值。这可能会导致意外清除您真正想要保留的数据。

处理损坏和不一致的 JSON

JSON 标准非常清楚如何区分字符串和数字——基本上,字符串用引号括起来,而数字不是。然而,JSON Web 服务并不总是能很好地满足这个要求。即使它们是一致的,它们也并不总是从一个记录到另一个记录。

一个例子,类似于我最近遇到的一个例子:衣服的尺码信息。大小由 Web 服务提供,通常为“30-32”、“34-36”等。这些在 JSON 中被正确引用为字符串,应用程序将它们保存为字符串属性并显示给用户照原样。

但有时尺寸只有一个数字,例如“8”、“10”等。在这种情况下,服务器会丢弃引号,使其成为数字。我的 JSON 解析器正确地生成了一个 NSNumber。只有我想把它保存在我实体的字符串属性中!我可以使用 Objective-C 内省来查看我是否从我的 JSON 解析器接收到 NSString 或 NSNumber,但我还需要知道托管对象期望的属性类型。我曾短暂地考虑过破坏我的 JSON 解析器,以便它始终返回 NSString,所以至少我会知道对它有什么期望。幸运的是 NSEntityDescription 又来了。

除了询问实体描述其属性名称是什么外,还可以查询Core Data模型中配置的属性类型。这作为 NSAttributeType 返回。使用它,我们可以扩展上面的代码来处理不匹配的数据类型。抽象代码以便于重用可能也是一个好主意,所以我将它放在 NSManagedObject 上的一个类别中:

@implementation NSManagedObject (safeSetValuesKeysWithDictionary)



- (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues


    NSDictionary *attributes = [[self entity] attributesByName];
    for (NSString *attribute in attributes) 
        id value = [keyedValues objectForKey:attribute];
        if (value == nil) 
            // Don't attempt to set nil, or you'll overwite values in self that aren't present in keyedValues
            continue;
        
        NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
        if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) 
            value = [value stringValue];
         else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) 
            value = [NSNumber numberWithInteger:[value  integerValue]];
         else if ((attributeType == NSFloatAttributeType) && ([value isKindOfClass:[NSString class]])) 
            value = [NSNumber numberWithDouble:[value doubleValue]];
        
        [self setValue:value forKey:attribute];
    

@end

此代码检查我们从传入 JSON 创建的字典中找到的值的类型和托管对象上的属性,如果存在字符串/数字不匹配,它会修改值以确保它与预期匹配。

这变得非常有用。它不仅是一个通用的 JSON 到 NSManagedObject 的转换,它还可以处理数字和字符串类型之间的不匹配,而不需要任何实体特定的信息。不过,它可能会更好。 处理日期

JSON 没有日期类型,但日期在 JSON 中很常见。它们只是使用一种或另一种日期格式表示为字符串。只有你可能想要一个 NSDate,而不是一个字符串。 NSDateFormatter 在这里真的很有用,但是让日期转换也通用化不是很好吗,所以你不需要对你的日期属性进行特殊处理?你能看出我要做什么吗?

已经编写了上面的 category 方法,添加一个可选的 NSDateFormatter 参数,然后在实体的属性需要一个日期时使用它并没有太多工作。这个修改后的版本在“else ... if”链中添加了该参数和一个额外的案例:

@implementation NSManagedObject (safeSetValuesKeysWithDictionary)

- (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter

    NSDictionary *attributes = [[self entity] attributesByName];
    for (NSString *attribute in attributes) 
        id value = [keyedValues objectForKey:attribute];
        if (value == nil) 
            continue;
        
        NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
        if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) 
            value = [value stringValue];
         else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) 
            value = [NSNumber numberWithInteger:[value integerValue]];
         else if ((attributeType == NSFloatAttributeType) &&  ([value isKindOfClass:[NSString class]])) 
            value = [NSNumber numberWithDouble:[value doubleValue]];
         else if ((attributeType == NSDateAttributeType) && ([value isKindOfClass:[NSString class]]) && (dateFormatter != nil)) 
            value = [dateFormatter dateFromString:value];
        
        [self setValue:value forKey:attribute];
    

@end

【讨论】:

以上是关于使用 RSAPI 将 JSON 保存到 Core Data的主要内容,如果未能解决你的问题,请参考以下文章

如何将用户登录的 JSON 响应保存到 Core Data 中以跟踪 Swift 中的用户状态?

将 jQuery JSON 对象保存到 SQL 表中,而无需在 MVC ASP.NET Core 中创建视图模型类

将 IConfigurationRoot 部分的更改保存到 .net Core 2.2 中的 *.json 文件

在 SwiftUI 中将加载的 json 文件中的更改保存到 Core Data

在使用 NSFetchedResultsController 更新 UITableView 时将来自 Web 的数据保存到 Core Data

从 Core Data 创建一个 json 文件