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