Обработка NSNull для значений свойств NSManagedObject - PullRequest
35 голосов
/ 04 февраля 2012

Я устанавливаю значения для свойств моего 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] или, возможно, сделать это совсем по-другому, но проще?

Ответы [ 5 ]

65 голосов
/ 04 февраля 2012

Может быть немного проще, если вы заключите это в макрос:

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

Тогда вы можете написать что-то вроде

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

В качестве альтернативы вы можете предварительно обработать свой словарь изамените все NSNull на nil, прежде чем пытаться вставить их в управляемый объект.

8 голосов
/ 04 февраля 2012

Хорошо, я только что проснулся сегодня утром с хорошим решением. Как насчет этого:

Сериализация 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) или nil, в отличие от <null> или [NSNull null].

Не относительно этого, но я также заметил, что лучше использовать NSNumberFormatter при синтаксическом анализе строк в NSNumber, так как я получал integerValue из строки nil, это дает мне нежелательный NSNumber 0, когда я действительно хотел, чтобы это было ноль.

До:

// 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
1 голос
/ 04 февраля 2012

Я написал пару методов категории для удаления пустых значений из сгенерированного 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 имели такое поведение по умолчанию - почти всегда предпочтительнее опускать нулевые объекты, чем включать их в качестве NSNulls.

0 голосов
/ 03 марта 2015

Я застрял с той же проблемой, нашел этот пост, сделал это немного по-другому. Используя только категорию -

Создайте новый файл категории для «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"];

Вот так

0 голосов
/ 10 февраля 2014

Другой метод -

-[NSObject setValuesForKeysWithDictionary:]

В этом сценарии вы могли бы сделать

[fight setValuesForKeysWithDictionary:dict];

В заголовке NSKeyValueCoding.h он определяет, что «словарные записи, значения которых NSNull, приводят к отправке -setValue:nil forKey:key сообщений получателю.

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

dict[@"winnerID"] = dict[@"winner"];
[dict removeObjectForKey:@"winner"];
...