How to determine the true data type of an NSNumber?

前端 未结 4 1475
悲哀的现实
悲哀的现实 2020-12-02 00:22

Consider this code:

NSNumber* interchangeId = dict[@\"interchangeMarkerLogId\"];
long long llValue = [interchangeId longLongValue];
double dValue = [intercha         


        
相关标签:
4条回答
  • 2020-12-02 01:08

    Ok--It's not 100% ideal, but you add a little bit of code to SBJSON to achieve what you want.

    1. First, add NSNumber+SBJson to the SBJSON project:

    NSNumber+SBJson.h

    @interface NSNumber (SBJson)
    @property ( nonatomic ) BOOL isDouble ;
    @end
    

    NSNumber+SBJson.m

    #import "NSNumber+SBJSON.h"
    #import <objc/runtime.h>
    
    @implementation NSNumber (SBJson)
    
    static const char * kIsDoubleKey = "kIsDoubleKey" ;
    
    -(void)setIsDouble:(BOOL)b
    {
        objc_setAssociatedObject( self, kIsDoubleKey, [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
    }
    
    -(BOOL)isDouble
    {
        return [ objc_getAssociatedObject( self, kIsDoubleKey ) boolValue ] ;
    }
    
    @end
    

    2. Now, find the line in SBJson4StreamParser.m where sbjson4_token_real is handled. Change the code as follows:

    case sbjson4_token_real: {
        NSNumber * number = @(strtod(token, NULL)) ;
        number.isDouble = YES ;
        [_delegate parserFoundNumber:number ];
        [_state parser:self shouldTransitionTo:tok];
        break;
    }

    note the bold line... this will mark a number created from a JSON real as a double.

    3. Finally, you can check the isDouble property on your number objects decoded via SBJSON

    HTH

    edit:

    (Of course you could generalize this and replace the added isDouble with a generic type indicator if you like)

    0 讨论(0)
  • 2020-12-02 01:18

    NSJSONSerializer returns:

    an integer NSNumber for integers up to 18 digits

    an NSDecimalNumber for integers with 19 or more digits

    a double NSNumber for numbers with decimals or exponent

    a BOOL NSNumber for true and false.

    Compare directly with the global variables kCFBooleanFalse and kCFBooleanTrue (spelling might be wrong) to find booleans. Check isKindOfClass:[NSDecimalNumber class] for decimal numbers; these are actually integers. Test

    strcmp (number.objCType, @encode (double)) == 0
    

    for double NSNumbers. This will unfortunately match NSDecimalNumber as well, so test that first.

    0 讨论(0)
  • 2020-12-02 01:19

    Simple answer: You can't.

    In order to do what you're asking, you'll need to keep track of the exact type on your own. NSNumber is more of a "dumb" wrapper in that it helps you use standard numbers in a more objective way (as Obj-C objects). Using solely NSNumber, -objCType is your only way. If you want another way, you'd have to do it on your own.

    Here are some other discussions that may be of help:

    get type of NSNumber

    What's the largest value an NSNumber can store?

    Why is longLongValue returning the incorrect value

    NSJSONSerialization unboxes NSNumber?

    0 讨论(0)
  • 2020-12-02 01:19

    As documented in NSDecimalNumber.h, NSDecimalNumber always returns "d" for it's return type. This is expected behavior.

    - (const char *)objCType NS_RETURNS_INNER_POINTER;
        // return 'd' for double
    

    And also in the Developer Docs:

    Returns a C string containing the Objective-C type of the data contained in the
    receiver, which for an NSDecimalNumber object is always “d” (for double).
    

    CFNumberGetValue is documented to return false if the conversion was lossy. In the event of a lossy conversion, or when you encounter an NSDecimalNumber, you will want to fall back to using the stringValue and then use sqlite3_bind_text to bind it (and use sqlite's column affinity).

    Something like this:

    NSNumber *number = ...
    BOOL ok = NO;
    
    if (![number isKindOfClass:[NSDecimalNumber class]]) {
        CFNumberType numberType = CFNumberGetType(number);
    
        if (numberType == kCFNumberFloat32Type ||
            numberType == kCFNumberFloat64Type ||
            numberType == kCFNumberCGFloatType)
        {
            double value;
            ok = CFNumberGetValue(number, kCFNumberFloat64Type, &value);
    
            if (ok) {
                ok = (sqlite3_bind_double(pStmt, idx, value) == SQLITE_OK);
            }
    
        } else {
            SInt64 value;
            ok = CFNumberGetValue(number, kCFNumberSInt64Type, &value);
    
            if (ok) {
                ok = (sqlite3_bind_int64(pStmt, idx, value) == SQLITE_OK);
            }
        }
    }
    
    // We had an NSDecimalNumber, or the conversion via CFNumberGetValue() was lossy.
    if (!ok) {
        NSString *stringValue = [number stringValue];
        ok = (sqlite3_bind_text(pStmt, idx, [stringValue UTF8String], -1, SQLITE_TRANSIENT) == SQLITE_OK);
    }
    
    0 讨论(0)
提交回复
热议问题