if statement for dictionary objectforkey:#"key" when value is <null> - objective-c

I am working with objective c for an iphone app.
I see that [dictionary objectForKey:#"key"] return <null>. Doing a if([dictionary objectForKey:#"key"] == nil || [dictionary objectForKey:#"key"] == null) does not seem to catch this case.
Doing a if([[dictionary objectForKey:#"key"] isEqualToString:#"<null>"]) causes my program to crash.
What is the correct expression to catch <null>?
More Details
An if statement for nil still isn't catching the case... Maybe i'm just too tired to see something, but here's additional info:
Dictionary is populated via a url that contains json data like so:
NSURL *url = [NSURL URLWithString:"http://site.com/"];
dataresult = [NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:dataresult options:kNilOptions error:& error];
doing an NSLog on the dictionary gives this output:
{
key = "<null>";
responseMessage = "The email / registration code combination is incorrect";
}

You have an instance of NSNull. Actually, the instance, since it's a singleton.
Cocoa collections can't contain nil, although they may return nil if you try to access something which isn't present.
However, sometimes it's valuable for a program to store a thing meaning "nothing" in a collection. That's what NSNull is for.
As it happens, JSON can represent null objects. So, when converting JSON to a Cocoa collection, those null objects get translated into the NSNull object.
When Cocoa formats a string with a "%#" specifier, a nil value will get formatted as "(null)" with parentheses. An NSNull value will get formatted as "<null>" with angle brackets.

New answer:
Thanks for adding the detail. It looks like the "dataresult" you are setting is not a JSON object so no wonder you're getting wacky results from putting a raw string into "[NSJSONSerialization JSONObjectWithData:]. You may need to do some basic error checking on your data before you call anything JSON related.
Original answer:
First off, if this were my code, I wouldn't name a "NSDictionary" object "array" (and I see you caught my comment in your edit... hope you get some sleep soon!).
Secondly, what you are looking for is "nil", not a string named "<null>". As Apple's documentation for objectForKey: states, if an object is not found for the key you are asking for, a nil is returned. The Xcode console tries to be helpful in converting nil objects to "<null>" strings in it's output.
Do "if [dictionary objectForKey: #"key"] != nil" and you should be happier.

Just use the following code:
if ([[dictionary valueForKey:#"key"] isKindOfClass:[NSNull Class]]{
//This means that the value inside the dictionary is <null>
}
else{
//the value is not <null>
}
This should do it.

Related

Objective-C - "success" not equal to "success"

The basic gist: I've got some JSON coming back from a webservice to validate a login; that part works. I'm pulling values out of the array into an NSDictionary; that part works. I need to check one of the values that comes back to know if it was successful or not. That's where it's failing. And as far as I can tell, it's telling me that "success" is not equal to "success".
NSDictionary *jsonArray = [NSJSONSerialization JSONObjectWithData: response options: NSJSONReadingMutableContainers error: &err];
NSString *result = [jsonArray valueForKey:#"result"];
NSLog(#"%#",result);
if ([result isEqual:#"success"]) {
The log shows "result" is getting set as "success", but it never seems to evaluate as true.
If I set "result" manually:
NSString *result = #"success";
...it gets into the if statement just fine, so it seems like there's something I'm missing that's pointing to a data type or something similar... I'm just at a loss of what else to try at this point.
I'm normally a web dev, but I'm new to iOS, so my debugging is still a little lacking in xcode but I'm familiar with general logic and such. Any help you guys could give me would be fantastic!
Edit:
NSLog showing the JSON coming back from the webservice:
2014-01-10 16:22:42.568 LoginTest[1640:70b] (
{
code = 1;
fname = Joe;
lname = Tests;
result = success;
token = 2555f13bce42b14cdc9e60b923bb2b20;
vendornum = 50000000;
}
)
Edit - final working code:
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: response options: NSJSONReadingMutableContainers error: &err];
NSLog(#"jsonArray: %#", jsonArray);
NSString *result = [jsonArray[0] objectForKey:#"result"];
NSLog(#"%#",result);
if ([result isEqual:#"success"]) {
Earlier you commented (from a now removed answer) that -isEqualToString: threw an unrecognized selector. I believe it was -[__NSCFArray -isEqualToString:] or something very similar.
Based on your comment, you don't have "success", you have [ "success" ] in your JSON.
That is an array which wraps the value of a string. You need to get the first element of the array and use that.
[result[0] isEqual:#"success"]
Based on the output in your log, your JSON is not an object
{
…
"result" = "success"
…
}
It is an array with only one object in it.
[
{
…
"result" = "success"
…
}
]
You are working with an array of data so the output of -valueForKey: will be an array of data.
#MartinR is correct, it may be clearer to use
[jsonArray[0] objectForKey:#"result"]
to get the result.
You didn't show us the actual output of the log. That's bad. Deducing from your comments, it should have shown something like
(
"success"
)
which is the description of an array object (NSArray) containing a string, rather than the string object itself.
If this is indeed the case, then you need to get the (only? first?) element in the array and compare that using isEqual: or isEqualToString:.
if we want to compare two NSString we use [str1 isEqualToString:str2]; You should do same instead of isEqual:
isEqual: compares a string to an object, and will return NO if the object is not a string. do it if you are not sure if object is NSString.
`isEqualToString:` use it when you are sure both Objects are NSString.

-[NSNull objectForKeyedSubscript:]: unrecognized selector sent to instance

I got an exception that says:
-[NSNull objectForKeyedSubscript:]: unrecognized selector sent to instance
Is it saying I am trying to access an NSNull object with a key?
Any idea what causes this and how to fix it or debug further?
The way to fix it is to not attempt objectForKeyedSubscript on an NSNull object. (I'm betting you're handling some JSON data and aren't prepared for a NULL value.)
(And apparently objectForKeyedSubscript is what the new array[x] notation translates into.)
(Note that you can test for NSNull by simply comparing with == to [NSNull null], since there's one and only one NSNull object in the app.)
What ever value you are storing, despite what the editor tells you, at run time you are storing an NSNull, and later on trying to call objectForKeyedSubscript. I am guessing this happening on what is expected to be an NSDictionary. Some thing like:
NSString *str = dict[#"SomeKey"]
Either a piece of code beforehand is not doing its job and investigate there, or perform some validation:
NSDictionary *dict = ...;
if ( [dict isKindOfClass:[NSDictionary class]] ) {
// handle the dictionary
}
else {
// some kind of error, handle appropriately
}
I often have this kind of scenario when dealing with error messages from networking operations.
I suggest adding a category to NSNull to handle this in the same way you would expect a subscript call to be handled if it it were sent to nil.
#implementation NSNull (Additions)
- (NSObject*)objectForKeyedSubscript:(id<NSCopying>)key {
return nil;
}
- (NSObject*)objectAtIndexedSubscript:(NSUInteger)idx {
return nil;
}
#end
A simple way to test is like this:
id n = [NSNull null];
n[#""];
n[0];
With this category, this test should be handled successfully/softly.

NSSet with NSStrings containstObject not return YES when it should

I'm loading a dictionary (list of word, not the class) into a NSSet as NSStrings. I then repeatedly send this set the message -containsObject:someNSString. But it always returns false. I wrote some code to test it:
NSLog(#"Random from dictionary: %#", [dictionary anyObject]);
NSString *test = [NSString stringWithFormat:#"BEMIRED"];
NSLog(#"To match this word: %#", test);
if ([dictionary containsObject:test])
NSLog(#"YES!");
In the log I get the following:
Random from dictionary: BEMIRED
To match this word: BEMIRED
(I'm missing the "YES!")
When I try using CFShow(dictionary) I can see that it actually contains Strings and that everything. An example:
0 : <CFString 0xc3bd810 [0x1386400]>{contents = "BEMIRED"}
3 : <CFString 0xdf96ef0 [0x1386400]>{contents = "SUBJECTIFIED"}
Can anyone please help me here?
Thanks!
NSSet uses isEqual: to test for object equality, which NSString overrides to perform a string comparison as you would expect. The follow unit test passes:
- (void)testSetStrings
{
NSSet *set = [NSSet setWithObject:#"String 1"];
// I've used the UTF8 initializer to avoid any cleverness from reusing objects
NSString *string1 = [[[NSString alloc] initWithUTF8String:"String 1"] autorelease];
// Test the references/pointers are not the same
STAssertTrue([set anyObject] != string1, nil);
STAssertTrue([set containsObject:string1], nil);
}
We can see the two strings have different pointer values, but the set still returns YES for the containsObject: call.
So I would guess your strings are not in fact equal. I would check for hidden whitespace or other similar issues.
The -[NSSet containsObject:] seems to check for the pointer value only (the documentation is very lacking for that method), not for object equality. So you need to use -[NSSet member:] instead, which uses isEqual: to check whether an object that is considered to be equal is in your set.
if ([dictionary member:test])
NSLog(#"YES!");
Edit: Actually it seems that containsObject: does use isEqual: as well. They only seem to differ in what they return (containsObject: returns a BOOL while member: returns id). I'm letting this answer stay for documentation purposes.
Ok so I solved the problem and it had nothing to do with the containsObject method. As I commented i used Dave DeLongs DDFileReader found here: Dave DeLongs DDFileReader
So by using CFShow on the entire dictionary I noticed that every word had a new line at the end of it. So instead of the -readLine method i used the -readTrimmedLine (bot methods in above mentioned file reader). This solved the problem for me.
For future forum visitors I'd like to draw attention to the discussion DarkDust and zoul had about -containsObject and -member (both methods of NSSet) which it turns out both uses the -isEqual method.

TouchJSON, dealing with NSNull

Hi
I am using TouchJSON to deserialize some JSON. I have been using it in the past and on those occasions I dealt with occurrences of NSNull manually. I would think the author had to deal with this as well, so me doing that again would just be overhead. I then found this in the documentation:
Avoiding NSNull values in output.
NSData *theJSONData = /* some JSON data */
CJSONDeserializer *theDeserializer = [CJSONDeserializer deserializer];
theDeserializer.nullObject = NULL;
NSError *theError = nil;
id theObject = [theDeserializer deserialize:theJSONData error:&theError];}
The way I understand it the user of the class can pass a C-style null pointer to the deserializer and when it encounters a NSNull it will insert the values (NULL) passed to it. So later on when I use the values I won't get NSNull, but NULL.
This seems strange, the return value is an NSDictionary which can only contain Objects, shouldn't the value default to 'nil' instead?
If it is NULL can I check the values like this?
if([jsonDataDict objectForKey:someKey] == NULL)
It would seem more logically to be able to do this:
if(![jsonDataDict objectForKey:someKey])
No to mention all the cases where passing nil is allowed but passing NULL causes a crash.
Or can I just pass 'nil' to the deserializer?
Much of this stems from me still struggling with nil, NULL, [NSNULL null], maybe I am failing to see the potential caveats in using nil.
For another JSON library, but with the same issues, I've created the following category on NSDictionary:
#implementation NSDictionary (Utility)
// in case of [NSNull null] values a nil is returned ...
- (id)objectForKeyNotNull:(id)key {
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
#end
Whenever I deal with JSON data from said library, I retrieve values like this:
NSString *someString = [jsonDictionary objectForKeyNotNull:#"SomeString"];
This way the code in my projects become a lot cleaner and at the same time I don't have to think about dealing with [NSNull null] values and the like.
nil and NULL are actually both equal to zero, so they are, in practice, interchangeable. But you're right, for consistency, the documentation for TouchJSON should have used theDeserializer.nullObject = nil instead of NULL.
Now, when you do that, your second predicate actually works fine:
if (![jsonDataDict objectForKey:someKey])
because TouchJSON omits the key from the dictionary when you have nullObject set to nil (or NULL). When the key doesn't exist in the dictionary, NSDictionary returns nil, which is zero so your if condition works as you expect.
If you don't specify nullObject as nil, you can instead check for null like so:
if ([jsonDataDict objectForKey:someKey] == [NSNull null])
There are libraries which deal with it. One of them is SwiftyJSON in Swift, another one is NSTEasyJSON in Objective-C.
With this library (NSTEasyJSON) it will be easy to deal with such problems. In your case you can just check values you need:
NSTEasyJSON *JSON = [NSTEasyJSON withData:JSONData];
NSString *someValue = JSON[someKey].string;
This value will be NSString or nil and you should not check it for NSNull, NULL yourself.

Is if (variable) the same as if (variable != nil) in Objective-C

I am getting a EXC_BAD_ACCESS (SIGBUS) on this line in my iPhone project:
if (timeoutTimer) [timeoutTimer invalidate];
The thing that has me stumped is that I don't understand how that line could crash, since the if statement is meant to be checking for nil. Am I misunderstanding the way Objective-C works, or do line numbers in crash statements sometime have the wrong line in them?
Just because a variable is set to a value other than nil doesn't mean it's pointing to a valid object. For example:
id object = [[NSObject alloc] init];
[object release];
NSLog(#"%#", object); // Not nil, but a deallocated object,
// meaning a likely crash
Your timer has probably already been gotten rid of (or possibly hasn't been created at all?) but the variable wasn't set to nil.
I just ran into a similar issue, so here's another example of what might cause a check such as yours to fail.
In my case, I was getting the value from a dictionary like this:
NSString *text = [dict objectForKey:#"text"];
Later on, I was using the variable like this:
if (text) {
// do something with "text"
}
This resulted in a EXC_BAD_ACCESS error and program crash.
The problem was that my dictionary used NSNull values in cases where an object had an empty value (it had been deserialized from JSON), since NSDictionary cannot hold nil values. I ended up working around it like this:
NSString *text = [dict objectForKey:#"text"];
if ([[NSNull null] isEqual:text]) {
text = nil;
}
They should be the same. Perhaps the line number is in fact incorrect.
Look for other possible errors near that in your code and see if you find anything.