35

我正在为我的属性设置值NSManagedObject,这些值来自一个NSDictionary从 JSON 文件正确序列化的值。我的问题是,当某个值为 时[NSNull null],我不能直接分配给该属性:

    fight.winnerID = [dict objectForKey:@"winner"];

这抛出了一个NSInvalidArgumentException

"winnerID"; desired type = NSString; given type = NSNull; value = <null>;

我可以轻松地检查[NSNull null]并分配的值nil

fight.winnerID = [dict objectForKey:@"winner"] == [NSNull null] ? nil : [dict objectForKey:@"winner"];

但我认为这并不优雅,而且需要设置很多属性。

此外,在处理NSNumber属性时这会变得更加困难:

fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:@"round"] unsignedIntegerValue]]

现在NSInvalidArgumentException是:

[NSNull unsignedIntegerValue]: unrecognized selector sent to instance

在这种情况下,我必须在对其进行评估[dict valueForKey:@"round"]之前进行处理NSUInteger。并且单线解决方案消失了。

我尝试制作一个@try @catch 块,但是一旦第一个值被捕获,它就会跳过整个@try 块并且忽略下一个属性。

有没有更好的方法来处理[NSNull null]或者可能使这完全不同但更容易?

4

5 回答 5

67

如果将其包装在宏中可能会更容易一些:

#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })

然后你可以写像

fight.winnerID = NULL_TO_NIL([dict objectForKey:@"winner"]);

或者,您可以在尝试将其填充到托管对象之前预处理您的字典并替换所有NSNulls 。nil

于 2012-02-04T02:58:54.237 回答
8

好的,我今天早上刚醒来,有一个很好的解决方案。那这个呢:

使用接收可变数组和字典的选项序列化 JSON:

NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...

[NSNull null]从leafDict获取一组具有值的键:

NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
    return [obj isEqual:[NSNull null]] ? YES : NO;
}];

从 Mutable LeafDict 中删除过滤后的属性:

[leafDict removeObjectsForKeys:[nullSet allObjects]];

现在,当您致电时,fight.winnerID = [dict objectForKey:@"winner"];winnerID 将自动成为(null)ornil而不是<null>or [NSNull null]

与此无关,但我也注意到在将NSNumberFormatter字符串解析为 NSNumber 时最好使用 a,我所做的方式是integerValue从 nil 字符串中获取,这给了我一个不想要的 NSNumber of 0,而我实际上希望它为 nil .

前:

// when [leafDict valueForKey:@"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:@"round"] integerValue]]
// Result: fight.round = 0

后:

__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:@"round"]];    
// Result: fight.round = nil
于 2012-02-04T16:15:10.630 回答
1

在使用之前,我编写了几个类别方法来从 JSON 生成的字典或数组中去除空值:

@implementation NSMutableArray (StripNulls)

- (void)stripNullValues
{
    for (int i = [self count] - 1; i >= 0; i--)
    {
        id value = [self objectAtIndex:i];
        if (value == [NSNull null])
        {
            [self removeObjectAtIndex:i];
        }
        else if ([value isKindOfClass:[NSArray class]] ||
                 [value isKindOfClass:[NSDictionary class]])
        {
            if (![value respondsToSelector:@selector(setObject:forKey:)] &&
                ![value respondsToSelector:@selector(addObject:)])
            {
                value = [value mutableCopy];
                [self replaceObjectAtIndex:i withObject:value];
            }
            [value stripNullValues];
        }
    }
}

@end


@implementation NSMutableDictionary (StripNulls)

- (void)stripNullValues
{
    for (NSString *key in [self allKeys])
    {
        id value = [self objectForKey:key];
        if (value == [NSNull null])
        {
            [self removeObjectForKey:key];
        }
        else if ([value isKindOfClass:[NSArray class]] ||
                 [value isKindOfClass:[NSDictionary class]])
        {
            if (![value respondsToSelector:@selector(setObject:forKey:)] &&
                ![value respondsToSelector:@selector(addObject:)])
            {
                value = [value mutableCopy];
                [self setObject:value forKey:key];
            }
            [value stripNullValues];
        }
    }
}

@end

如果标准 JSON 解析库在默认情况下具有这种行为,那就太好了 - 忽略空对象几乎总是比将它们包含为 NSNull 更可取。

于 2012-02-04T03:25:38.277 回答
0

另一种方法是

-[NSObject setValuesForKeysWithDictionary:]

在这种情况下,你可以做

[fight setValuesForKeysWithDictionary:dict];

在标题 NSKeyValueCoding.h 中,它定义了“值为 NSNull 的字典条目会导致-setValue:nil forKey:key将消息发送到接收者。

唯一的缺点是您必须将字典中的任何键转换为接收器中的键。IE

dict[@"winnerID"] = dict[@"winner"];
[dict removeObjectForKey:@"winner"];
于 2014-02-10T07:19:37.237 回答
0

我遇到了同样的问题,找到了这篇文章,以稍微不同的方式做到了。虽然只使用类别 -

为“NSDictionary”创建一个新的类别文件并添加这个方法 -

@implementation NSDictionary (SuperExtras)

- (id)objectForKey_NoNSNULL:(id)aKey
{
    id result = [self objectForKey:aKey];

if(result==[NSNull null])
{
    return nil;
}

return result;

}

@end

稍后在代码中使用它,对于可以在其中包含 NSNULL 的属性,只需以这种方式使用它 -

newUser.email = [loopdict objectForKey_NoNSNULL:@"email"];

就是这样

于 2015-03-03T14:07:17.957 回答