PDA

View Full Version : [Resolved] Empty number fields and NSJSONSerialization




brontosaurus
Mar 2, 2012, 01:45 AM
I'm using Apple's relatively new Foundation class NSJSONSerialization to convert JSON data coming from the Rotten Tomatoes web service. I'm having trouble with empty number fields (e.g., the year field is sometimes empty).

Setup: Xcode 4.2, ARC, Rotten Tomatoes API is a RESTful web service

Here's the conversion using the aforementioned class:

NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:self.receivedData options:0 error:&error];


If I log the json dictionary object, I can see an empty year field (note: this is just a very small portion of the logged NSDictionary object):

title = "Heart of the Lion (Coeur de lion)";
year = "";


But how is empty represented? By an NSNull object? However, the following conditional will not evaluate to YES for that empty year field (note: aMovie is a dictionary object that I parsed out of the json dictionary object; it contains all the information about a single movie):

if ([aMovie objectForKey:@"year"] == [NSNull null])
NSLog(@"year is null!");


If I execute the following code an exception is thrown because the year field is empty:

NSNumber *year = [aMovie objectForKey:@"year"];
result.year = [year stringValue]; // throws exception


So what I have been doing is catching that exception (i.e., @try and @catch), but there has got to be a better way to handle an empty number field.

Thanks.



PhoneyDeveloper
Mar 2, 2012, 07:24 AM
I haven't used Apple's JSON class yet but you should be able to figure out what the class is for the year. I'll guess that it's a string not a number. It may be that it's a number if valid and a string if invalid, which might be a pain.

Anyway, what is the class of the year object? This should be visible in the variables pane in the debugger. Otherwise, what is the text that appears in the debugger console for the exception? These should tell you the class.

chown33
Mar 2, 2012, 09:24 AM
In a JSON representation, "" is an empty string. It's as simple as that.
http://json.org/


BTW, the following isn't JSON:
title = "Heart of the Lion (Coeur de lion)";
year = "";

It's the output from NSDictionary. It's no longer JSON. It stopped being JSON when the serialization converted the JSON text (in an NSData) into an NSDictionary.

I can tell it's not JSON because of the semicolons, which JSON doesn't have.

brontosaurus
Mar 2, 2012, 01:33 PM
I haven't used Apple's JSON class yet but you should be able to figure out what the class is for the year. I'll guess that it's a string not a number. It may be that it's a number if valid and a string if invalid, which might be a pain.


Yes, you're right. If the year field is not empty, it's converted to an NSNumber; if the year field is empty, it's converted to a string. But I still can't create a conditional that will catch it.

When the year field is empty, this is what the debugger tells me about it:
year = (__NSCFConstantString *)

However, none of the following conditionals evaluate to YES:

if ([year isMemberOfClass:[NSConstantString class]])
NSLog(@"It's a constant string");
if ([year isMemberOfClass:[NSString class]])
NSLog(@"It's a string");


Any suggestions?

brontosaurus
Mar 2, 2012, 01:48 PM
In a JSON representation, "" is an empty string. It's as simple as that.
http://json.org/


BTW, the following isn't JSON:
title = "Heart of the Lion (Coeur de lion)";
year = "";

It's the output from NSDictionary. It's no longer JSON. It stopped being JSON when the serialization converted the JSON text (in an NSData) into an NSDictionary.

I can tell it's not JSON because of the semicolons, which JSON doesn't have.

You missed my parenthetical comment in my original post where I state: this is just a very small portion of the logged NSDictionary object. Regardless, your point is a good one and definitely worth emphasizing.

You're right about the empty field being a string. When the year field is empty it's converted to a constant string; when the year is not empty, it's converted to a NSNumber object.

When the year field is empty, this is what the debugger tells me about it:
year = (__NSCFConstantString *)

However, I'm still having trouble catching an empty year field with a conditional. None of the following evaluate to YES:


if ([year isMemberOfClass:[NSConstantString class]])
NSLog(@"It's a constant string");
if ([year isMemberOfClass:[NSString class]])
NSLog(@"It's a string");


Any suggestions?

dejo
Mar 2, 2012, 01:56 PM
You've declared year as an NSNumber. Why would you expect its class to change?

brontosaurus
Mar 2, 2012, 02:07 PM
You've declared year as an NSNumber. Why would you expect its class to change?

According to the debugger, it does change. NSNumber year points to a constant string if the year field is empty and a NSNumber if the year field is not empty.

I've declared year as id type and I get the same results in the debugger: either a constant string or a NSNumber.

chown33
Mar 2, 2012, 02:09 PM
However, I'm still having trouble catching an empty year field with a conditional. None of the following evaluate to YES:


if ([year isMemberOfClass:[NSConstantString class]])
NSLog(@"It's a constant string");
if ([year isMemberOfClass:[NSString class]])
NSLog(@"It's a string");


Any suggestions?

Don't use isMemberOfClass: . Use isKindOfClass: and specify only the NSString class.

Why? Because isMemberOfClass looks for membership in a single specific class. isKindOfClass, however, looks for membership in a class, or any subclass of that class. So this:
if ([year isKindOfClass:[NSString class]])
NSLog(@"It's a string");
checks for any of the NSString subclasses. This is what you want when dealing with unspecified subclasses or class clusters (and NSString is a class cluster).

Of course, if the year object is an NSNumber, then the test will still yield NO.

brontosaurus
Mar 2, 2012, 02:36 PM
Don't use isMemberOfClass: . Use isKindOfClass: and specify only the NSString class.

Why? Because isMemberOfClass looks for membership in a single specific class. isKindOfClass, however, looks for membership in a class, or any subclass of that class. So this:
if ([year isKindOfClass:[NSString class]])
NSLog(@"It's a string");
checks for any of the NSString subclasses. This is what you want when dealing with unspecified subclasses or class clusters (and NSString is a class cluster).

Of course, if the year object is an NSNumber, then the test will still yield NO.

chown33 you're awesome! You solved my problem. I've been working on this problem off and on for over 2 weeks.

So here is the solution:

id year = [aMovie objectForKey:@"year"];

if ([year isKindOfClass:[NSString class]]) {
NSLog(@"is string !!!!!!!!!!!!!!!");
// treat year as a string object
} else {
// treat year as a number object
}


So thank you.

But dejo brings up a good point regarding my year variable not changing classes: how can my NSNumber year variable point to a string object? For example, the above solution works even if I change year to NSNumber:


NSNumber *year = [aMovie objectForKey:@"year"];

if ([year isKindOfClass:[NSString class]]) {
NSLog(@"is string !!!!!!!!!!!!!!!");
// year points to a string even though year is declared as type NSNumber
} else {
// treat year as a NSNumber object
}

Is Objective-C very permissive or what?

chown33
Mar 2, 2012, 03:59 PM
Look at the type that objectForKey: returns. It's id.

You're assuming it's NSNumber, because that's what you expect. But there's nothing in NSDictionary, or JSON parsing, or Objective-C or Foundation that requires the returned object to actually match your declared type, except in certain circumstances.

You should look more carefully at what it means when a method returns id. Specifically, what checks (if any) are made that the returned type on the right-hand side of the assignment (i.e. the = operator) matches the declared type on the left-hand side.

The short answer is that Objective-C is permissive with id, but not with other referential types. If you were expecting something else, then you're making assumptions based on other languages, or you haven't studied Objective-C typing in any detail. You really need to read a language reference doc about this, so you fully understand what and why.

1. Start here: https://developer.apple.com/library/ios/navigation/
2. Click the Guides sub-heading.
3. Enter objective-c in the search-terms box.

I see 6 reference guides, all of which you should read, though you can skip Blocks Programming Guide for now.

PhoneyDeveloper
Mar 2, 2012, 04:14 PM
It seems that NSNumber is the interesting case, not NSString, so I would be checking for NSNumber. Also, what happens if there is no year object? Make sure your code does the right thing.