Подтвердить что ты не робот

Обработка NSNull для значений свойств NSManagedObject

Я устанавливаю значения для свойств моего NSManagedObject, эти значения исходят из NSDictionary, правильно сериализованного из файла JSON. Моя проблема в том, что, когда какое-то значение [NSNull null], я не могу напрямую назначить свойство:

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

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

4b9b3361

Ответ 1

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

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

Затем вы можете писать такие вещи, как

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

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

Ответ 2

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

Сериализовать JSON с помощью опции для получения Mutable Arrays и Dictionaries:

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"];, идентификатор победителя автоматически будет (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

Ответ 3

Я написал несколько методов категорий для удаления нулей из 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.

Ответ 4

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

-[NSObject setValuesForKeysWithDictionary:]

В этом случае вы можете сделать

[fight setValuesForKeysWithDictionary:dict];

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

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

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

Ответ 5

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

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

Это он