objective c - Manejo de NSNull para valores de propiedades de NSManagedObject
objective-c json (5)
Escribí un par de métodos de categorías para quitar nulos de un diccionario o matriz generada por JSON antes de su uso:
@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
Sería bueno si las librerías de análisis JSON estándar tuvieran este comportamiento por defecto; casi siempre es preferible omitir objetos nulos que incluirlos como NSNulls.
Estoy estableciendo valores para las propiedades de mi NSManagedObject
, estos valores provienen de un NSDictionary
correctamente serializado a partir de un archivo JSON. Mi problema es que, cuando un valor es [NSNull null]
, no puedo asignarlo directamente a la propiedad:
fight.winnerID = [dict objectForKey:@"winner"];
esto arroja una NSInvalidArgumentException
"winnerID"; desired type = NSString; given type = NSNull; value = <null>;
Podría verificar fácilmente el valor de [NSNull null]
y asignarle nil
:
fight.winnerID = [dict objectForKey:@"winner"] == [NSNull null] ? nil : [dict objectForKey:@"winner"];
Pero creo que esto no es elegante y se complica con muchas propiedades para establecer.
Además, esto se vuelve más difícil cuando se trata de propiedades NSNumber
:
fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:@"round"] unsignedIntegerValue]]
La NSInvalidArgumentException
es ahora:
[NSNull unsignedIntegerValue]: unrecognized selector sent to instance
En este caso, tengo que tratar [dict valueForKey:@"round"]
antes de hacer un valor NSUInteger
de este. Y la solución de una línea se ha ido.
Intenté hacer un bloque @try @catch, pero tan pronto como se captura el primer valor, salta todo el bloque @try y las siguientes propiedades se ignoran.
¿Hay una manera mejor de manejar [NSNull null]
o quizás hacer esto completamente diferente pero más fácil?
Estaba atrapado con el mismo problema, encontré esta publicación, lo hice de una manera ligeramente diferente. Sin embargo, solo uso de categoría:
Crea un nuevo archivo de categoría para "NSDictionary" y agrega este único método:
@implementation NSDictionary (SuperExtras)
- (id)objectForKey_NoNSNULL:(id)aKey
{
id result = [self objectForKey:aKey];
if(result==[NSNull null])
{
return nil;
}
return result;
}
@end
Más tarde, para usarlo en el código, para las propiedades que pueden tener NSNULL en ellas, solo utilícelas de esta forma:
newUser.email = [loopdict objectForKey_NoNSNULL:@"email"];
Eso es
Ok, acabo de despertarme esta mañana con una buena solución. ¿Qué tal esto?
Serialice el JSON utilizando la opción para recibir matrices mutables y diccionarios:
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...
Obtenga un conjunto de claves que tengan [NSNull null]
de la etiqueta de hoja:
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj isEqual:[NSNull null]] ? YES : NO;
}];
Elimine las propiedades filtradas de su mutable leafDict:
[leafDict removeObjectsForKeys:[nullSet allObjects]];
Ahora cuando llamas a fight.winnerID = [dict objectForKey:@"winner"];
winnerID va a ser automáticamente (null)
o nil
en lugar de <null>
o [NSNull null]
.
No en relación con esto, pero también me di cuenta de que es mejor usar un NSNumberFormatter
al analizar cadenas a NSNumber, la forma en que estaba haciendo era obtener integerValue
de una cadena nula, esto me da un NSNumber no deseado de 0
, cuando realmente lo quería ser nil.
Antes de:
// when [leafDict valueForKey:@"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:@"round"] integerValue]]
// Result: fight.round = 0
Después:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:@"round"]];
// Result: fight.round = nil
Otro método es
-[NSObject setValuesForKeysWithDictionary:]
En este escenario, podrías hacer
[fight setValuesForKeysWithDictionary:dict];
En el encabezado NSKeyValueCoding.h define que "las entradas del diccionario cuyos valores son NSNull dan como resultado -setValue:nil forKey:key
mensajes -setValue:nil forKey:key
que se envían al receptor.
El único inconveniente es que tendrá que transformar cualquier tecla del diccionario en teclas que están en el receptor. es decir
dict[@"winnerID"] = dict[@"winner"];
[dict removeObjectForKey:@"winner"];
Puede ser un poco más fácil si envuelve esto en una macro:
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
Entonces puedes escribir cosas como
fight.winnerID = NULL_TO_NIL([dict objectForKey:@"winner"]);
De forma alternativa, puede preprocesar su diccionario y reemplazar todos los NSNull
con nil
antes incluso de intentar rellenarlo en su objeto administrado.