NSNull handling for NSManagedObject properties values
It might be a little easier if you wrap this in a macro:
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
Then you can write things like
fight.winnerID = NULL_TO_NIL([dict objectForKey:@"winner"]);
Alternatively you can pre-process your dictionary and replace all NSNull
s with nil
before even trying to stuff it into your managed object.
Ok, I've just woke up this morning with a good solution. What about this:
Serialize the JSON using the option to receive Mutable Arrays and Dictionaries:
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];...
Get a set of keys that have [NSNull null]
values from the leafDict:
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) { return [obj isEqual:[NSNull null]] ? YES : NO;}];
Remove the filtered properties from your Mutable leafDict:
[leafDict removeObjectsForKeys:[nullSet allObjects]];
Now when you call fight.winnerID = [dict objectForKey:@"winner"];
winnerID is automatically going to be (null)
or nil
as opposed to <null>
or [NSNull null]
.
Not relative to this, but I also noticed that it is better to use a NSNumberFormatter
when parsing strings to NSNumber, the way I was doing was getting integerValue
from a nil string, this gives me an undesired NSNumber of 0
, when I actually wanted it to be nil.
Before:
// when [leafDict valueForKey:@"round"] == nilfight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:@"round"] integerValue]]// Result: fight.round = 0
After:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];fight.round = [numberFormatter numberFromString:[leafDict valueForKey:@"round"]]; // Result: fight.round = nil
I wrote a couple of category methods to strip nulls from a JSON-generated dictionary or array prior to use:
@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
It would be nice if the standard JSON parsing libs had this behaviour by default - it's almost always preferable to omit null objects than to include them as NSNulls.