Resolved Empty number fields and NSJSONSerialization

Discussion in 'iOS Programming' started by brontosaurus, Mar 1, 2012.

  1. brontosaurus, Mar 1, 2012
    Last edited: Mar 2, 2012

    macrumors newbie

    Joined:
    Mar 1, 2012
    Location:
    Yakima, WA (no where USA)
    #1
    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:
    Code:
     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):
    Code:
     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):
    Code:
    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:
    Code:
    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.
     
  2. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    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.
     
  3. macrumors 603

    Joined:
    Aug 9, 2009
    #3
    In a JSON representation, "" is an empty string. It's as simple as that.
    http://json.org/


    BTW, the following isn't JSON:
    Code:
     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.
     
  4. thread starter macrumors newbie

    Joined:
    Mar 1, 2012
    Location:
    Yakima, WA (no where USA)
    #4
    What an empty field is

    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:
    Code:
    if ([year isMemberOfClass:[NSConstantString class]])
          NSLog(@"It's a constant string");
    if ([year isMemberOfClass:[NSString class]])
          NSLog(@"It's a string");
    
    Any suggestions?
     
  5. brontosaurus, Mar 2, 2012
    Last edited: Mar 2, 2012

    thread starter macrumors newbie

    Joined:
    Mar 1, 2012
    Location:
    Yakima, WA (no where USA)
    #5
    it's not that simple

    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:

    Code:
    if ([year isMemberOfClass:[NSConstantString class]])
          NSLog(@"It's a constant string");
    if ([year isMemberOfClass:[NSString class]])
          NSLog(@"It's a string");
    
    Any suggestions?
     
  6. Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #6
    You've declared year as an NSNumber. Why would you expect its class to change?
     
  7. thread starter macrumors newbie

    Joined:
    Mar 1, 2012
    Location:
    Yakima, WA (no where USA)
    #7
    NSJSONSerialization changes the class

    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.
     
  8. macrumors 603

    Joined:
    Aug 9, 2009
    #8
    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:
    Code:
    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.
     
  9. thread starter macrumors newbie

    Joined:
    Mar 1, 2012
    Location:
    Yakima, WA (no where USA)
    #9
    almost resolved

    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:
    Code:
    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:

    Code:
    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?
     
  10. macrumors 603

    Joined:
    Aug 9, 2009
    #10
    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.
     
  11. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #11
    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.
     

Share This Page