objective-c json parsing nsmanagedobject nsnull

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.