Stepan Generalov
Stepan Generalov

Reputation: 489

How to distinguish boolean & integers after deserializing JSON?

So, here's the problem:

  1. __NSCFBoolean & __NSCFNumber are private
  2. -[NSNumber boolValue] will cast 0/1 to NO/YES
  3. It's not a question right now, that having false/true/0/1 as possible values in JSON is a bad idea
  4. So how should one correctly distinguish between deserialized boolean & integer values?

Upvotes: 2

Views: 1387

Answers (4)

Joakim
Joakim

Reputation: 3294

I came up with another solution:

id value_from_json = json[key];
if([value_from_json isKindOfClass[NSNumber class]]) {
    NSNumber *number_value = (NSNumber *)value_from_json;
    // Observe that the check is "isEqualToValue", not "isEqualToNumber"
    if([number_value isEqualToValue:@YES] || [number_value isEqualToValue:@NO]) {
        // It's a boolean
    } else {
        // It's not a boolean
    }
}

I have tested it and it can distingush between "true", true and 1. (and "false", false and 0)

Upvotes: 0

CRD
CRD

Reputation: 53000

Unfortunately there is no guarantee that you can determine the source type used to create an NSNumber. The NSNumber documentation states it is toll-free bridged with CFNumber, in other words an instance of one can be treated as an instance of the other (with appropriate bridge casts in the case of ARC). The documentation for both types clearly states that while the type of the value stored in an instance of either can be determined, by CFNumberGetType() or objCType, this may not be the same type as the instance was constructed with. This is from the CFNumber documentation:

The type specified in the call to CFNumberCreate is not necessarily preserved when a new CFNumber object is created—it uses whatever internal storage type the creation function deems appropriate.

If you examine CFNumberType, an enum to represent the type of value in a CFNumber, then there is no value for BOOL.

Further there is the type CFBoolean to store wrapped booleans, it is not defined as a subtype of CFNumber or mentioned in the documentation for either it or NSNumber.

While it transpires that currently if you create an NSNumber with a BOOL value the result has been observed to be one of the constant values defined by CFBoolean there is no documentation that states that this is the defined behaviour.

So while for curiosity sake you can try to figure it out, don't rely on any specific behaviour in production code.

Note: CFBoolean is defined as one of the Core Foundation property list types. It may be possible to prove through using the various toll-free bridge relationships etc. that a CFBoolean must be toll-free bridged to NSNumber. However I do not think that guarantees to you that a boolean from JSON must be a CFBoolean instance, though it probably is...

Upvotes: 1

Sulthan
Sulthan

Reputation: 130092

The answer is simple - don't.

Your data parser should know what value to expect on every place. Just don't mix numbers and booleans.

Upvotes: 1

Stepan Generalov
Stepan Generalov

Reputation: 489

tldr: Use kCFBooleanFalse & kCFBooleanTrue to detect booleans correctly!

NSNumber holding booleans are instances of __NSCFBoolean private class, which is a special beast - pointer to any instance of this class will be either kCFBooleanFalse or kCFBooleanTrue, which gives us ability to use this code(ARC):

if ([value isKindOfClass::[NSNumber class]]) 
{
    if ((__bridge CFBooleanRef)value == kCFBooleanTrue) 
    {
        // value == @YES
    } 
    else if ((__bridge CFBooleanRef)value == kCFBooleanFalse) 
    {
        // value == @NO
    }
    else
    {
       // value is other type of number (int, float, etc...)
    }
}

Upvotes: 2

Related Questions