views:

385

answers:

6

I have a class that I use to setup objects in an array. In this class I have a custom "initWithDictionary", where I parse a JSON dictionary. However, as I am running into NSNull, this crashes my app. To get around this, I set up a class that handles exceptions, so when a string is NSNull, it's replace it with @"". or -1 for integers.

This is my NullExtensions class:

@interface NSNull (valueExtensions)

-(int)intValue;
-(NSString *)stringValue;

@end

@implementation NSNull (valueExtensions)

-(int)intValue {
    return -1;
}

-(NSString*)stringValue {
    return @"";
}

@end

However, in my initWithDictionary method, the following code crashes my app:

self.bookTitle = [[parsedDictionary objectForKey:@"book_title"] stringValue];

It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string. Only if I do the following (and the string is not null):

self.bookTitle = [parsedDictionary objectForKey:@"book_title"];

Is stringValue incorrect in this case? And if so, how do I use it properly in order to setup proper NSNull replacements?

Thx

+1  A: 

You are asking an NSString for its stringValue. No need to convert a string to a string.

Try this:

if (![[parsedDictionary objectForKey:@"book_title"] isKindOfClass:[NSNull class]]) {
    self.bookTitle = [parsedDictionary objectForKey:@"book_title"];
} else {
    self.bookTitle = @"";
}

Edit: You should not use the category on NSNull you created. You don't need it, nor should you want it. If the source for the dictionary inserts NSNull instances, go ahead and use my code above. Normally you would expect to simple have no value inserted for the key, at which time you can simple see if [parsedDictionary objectForKey:@"book_title"] returns anything.

Johan Kool
That doesn't make sense? self.bookTitle is the objectForKey:@"book_title". It's still get the value even it's a null and when I try to use the bookTitle string, it'll crash.
Canada Dev
Ah, that's because you've inserted NSNull as an object in your dictionary, instead of simply omitting it. You can change the if statement to check wether the object is of class NSNull, than choose the right behavior.
Johan Kool
Makes more sense now that you edited it :)
Canada Dev
+2  A: 

The NSNull extensions you have written look ok to me but using a method like stringValue may be confusing since other classes like NSNumber use this.

Personally though, I think NSNull replacement in this instance is unnecessary. If you just made a quick test you can replace the NSNull where you need to. e.g.

id testObject = [parsedDictionary objectForKey:@"book_title"];
self.bookTitle = testObject==[NSNull null] ? @"" : testObject;
imnk
I think you have to test for `testObject==[NSNull null]` to properly identify the `NSNull` value. `NSNull` by itself is a Class, but the actual object stored in the dictionary would be the singleton instance `[NSNull null]`
e.James
good point - I've updated. thx.
imnk
+5  A: 

You really really don't want to add a category to NSNull that adds such common methods. That will change the behavior of NSNull for all instances in the application, including ones created by the underlying frameworks solely for their private use.

If you need a value class that represents the notion of "value doesn't exist and therefore I'm going to return these default values instead", create a class or instance that represents exactly that.

As for why it crashes, I couldn't tell you without seeing the actual details of the crash.


And, yes, it really is THAT bad to add a category to a class that adds such a common method. All it takes is one bit of code in a plug-in or framework that does:

if ([fooMaybeNull respondsToSelector: @selector(intValue)] bar = [fooMaybeNull intValue];

Not terribly farfetched -- I have had to debug nasty crashers or misbehaviors due to exactly this kind of willy-nilly category addition.

If you are going to add methods to a class via categories, prefix your method names so as to isolate them from existing functionality. It is still fragile, but manageably so.

bbum
OK, thanks for the advice.
Canada Dev
It's not THAT bad to create a category like this - it doesn't matter it exists for all other classes because they will never know about it to try calling! Furthermore, it's not really practical to say he should create a new type because this is being returned by the JSON library - it's not really practical to modify the library to add a whole new type, when NSNull works perfectly well for what it does.
Kendall Helmstetter Gelner
In any case, this is an abuse of categories. NSNull does not have a meaning 'stringValue' or 'intValue' in the general sense, so it shouldn't have such a method. Instead, the class dealing with this dictionary should be doing an 'obj == [NSNull null]' check rather than blindly sending message to objects of ambiguous types.
Jon Hess
A: 

Are you sure that the dictionary is returning [NSNull null]? By default, dictionaries return nil, not [NSNull null], when an value isn't found for a key.

Dave DeLong
Yes, it is NSNull - a JSON parser will use that to indicate a nil value as opposed to an empty string (which is different).
Kendall Helmstetter Gelner
+3  A: 

Instead of creating categories on NSNull, for which you would also have to add a similar category to NSString (that's why it crashes, because real strings do not respond to stringValue) - instead try creating a helper category on NSDictionary like "stringForKey" that uses the code Johan posted and returns an NSString, probably also should enforce all other types get mapped to empty strings as well.

Kendall Helmstetter Gelner
Seems like a good idea. Will try this later today
Canada Dev
Worked like a charm :)
Canada Dev
Again -- don't do this. It will bite you eventually and, when it does, it'll be a total headscratcher. If you are going to add such a method, prefix it.
bbum
To reinforce what bbum said: I’ve had a real-world problem where a -doubleValue category on NSDictionary caused a crash because a third-party input manager had a method of the same name but with different semantics. That one was fun.
Ahruman
A: 

However, in my initWithDictionary method, the following code crashes my app:

self.bookTitle = [[parsedDictionary objectForKey:@"book_title"] stringValue];

It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string.

That makes sense, since stringValue is not a valid method on NSString. It will work for NSValue and its subclasses, but not NSString.

e.James