1

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

4

1 回答 1

0

简单/危险的方式

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
于 2012-09-04T14:32:11.823 回答