问题 NSJSONSerialization是否将数字反序列化为NSDecimalNumber?


采取以下代码:

NSError *error;
NSString *myJSONString = @"{ \"foo\" : 0.1}";
NSData *jsonData = [myJSONString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *results = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];

我的问题是,是 results[@"foo"] NSDecimalNumber,或具有有限二进制精度的东西,如double或float?基本上,我有一个应用程序,需要随附的无损精度 NSDecimalNumber,并且需要确保JSON反序列化不会因为双精度/浮点数等而导致舍入。

例如。如果它被解释为一个浮点数,我会遇到这样的问题:

float baz = 0.1;
NSLog(@"baz: %.20f", baz);
// prints baz: 0.10000000149011611938

我试过口译 foo 作为NSDecimalNumber并打印结果:

NSDecimalNumber *fooAsDecimal = results[@"foo"];
NSLog(@"fooAsDecimal: %@", [fooAsDecimal stringValue]);
// prints fooAsDecimal: 0.1

但后来我发现了这个问题 stringValue 在...上 NSDecimalNumber 不会打印所有有效数字,例如......

NSDecimalNumber *barDecimal = [NSDecimalNumber decimalNumberWithString:@"0.1000000000000000000000000000000000000000000011"];
NSLog(@"barDecimal: %@", barDecimal);
// prints barDecimal: 0.1

...所以打印 fooAsDecimal 不告诉我是否 results[@"foo"] 是否在某种程度上由JSON解析器舍入到有限精度。

为了清楚起见,我意识到我可以使用字符串而不是JSON表示中的数字来存储foo的值,即 "0.1" 代替 0.1,然后使用 [NSDecimalNumber decimalNumberWithString:results[@"foo"]]。但是,我感兴趣的是NSJSONSerialization类如何反序列化JSON数字,所以我知道这是否真的有必要。


3569
2017-08-01 06:12


起源



答案:


NSJSONSerialization (和 JSONSerialization 在Swift中)遵循一般模式:

  1. 如果数字只有一个整数部分(没有小数或指数),请尝试将其解析为a long long。如果没有溢出,请返回 NSNumber 同 long long
  2. 尝试用双解析 strtod_l。如果它没有溢出,返回一个 NSNumber 同 double
  3. 在所有其他情况下,尝试使用 NSDecimalNumber 它支持更大范围的值,特别是最多38位的尾数和-128 ... 127之间的指数。

如果您查看人们发布的其他示例,您可以看到当值超出范围或精度时 double 你得到一个 NSDecimalNumber 背部。


6
2017-09-18 02:58





简短的回答是,如果需要NSDecimalNumber级别的精度,则不应序列化为JSON。 JSON只有一种数字格式:double,它的精度低于NSDecimalNumber。

只有学术兴趣的长答案,因为简短答案也是正确的答案,是“不一定”。 NSJSONSerialization有时会反序列化为NSDecimalNumber,但它没有记录,我还没有确定它所处的环境是什么。例如:

    BOOL boolYes = YES;
    int16_t int16 = 12345;
    int32_t int32 = 2134567890;
    uint32_t uint32 = 3124141341;
    unsigned long long ull = 312414134131241413ull;
    double dlrep = 1.5;
    double dlmayrep = 1.1234567891011127;
    float fl = 3124134134678.13;
    double dl = 13421331.72348729 * 1000000000000000000000000000000000000000000000000000.0;
    long long negLong = -632414314135135234;
    unsigned long long unrepresentable = 10765432100123456789ull;

    dict[@"bool"] = @(boolYes);
    dict[@"int16"] = @(int16);
    dict[@"int32"] = @(int32);
    dict[@"dlrep"] = @(dlrep);
    dict[@"dlmayrep"] = @(dlmayrep);
    dict[@"fl"] = @(fl);
    dict[@"dl"] = @(dl);
    dict[@"uint32"] = @(uint32);
    dict[@"ull"] = @(ull);
    dict[@"negLong"] = @(negLong);
    dict[@"unrepresentable"] = @(unrepresentable);

    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];

    NSDictionary *dict_back = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

并在调试器中:

(lldb) po [dict_back[@"bool"] class]
__NSCFBoolean
(lldb) po [dict_back[@"int16"] class]
__NSCFNumber
(lldb) po [dict_back[@"int32"] class]
__NSCFNumber
(lldb) po [dict_back[@"ull"] class]
__NSCFNumber
(lldb) po [dict_back[@"fl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dlrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"dlmayrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"negLong"] class]
__NSCFNumber
(lldb) po [dict_back[@"unrepresentable"] class]
NSDecimalNumber

所以你要做的就是这样。你绝对不应该假设如果你将NSDecimalNumber序列化为JSON,你将得到一个NSDecimalNumber。

但是,再次,您不应该将NSDecimalNumbers存储在JSON中。


3
2018-01-27 16:27



这是不正确的。 JSON格式不关心浮点数或双精度。这是失去精度的JSON解析器。 - Sulthan
@icodestuff:如果我使用你的代码运行 double dl = 8.92918e-128;, dict_back 是 nil。你有没有想过是否有办法让JSON反序列化使用这些值 NSJSONSerialization 还是其他图书馆? - sergio
正如Sulthan所说,JSON并不关心精度。如果Apple的API遵循规范,则应使用NSDecimalNumber。 - xtravar
如果你检查我的评论,它确实使用 NSDecimalNumber 它支持更大的范围但不同的精度特性。 - russbishop


答案:


NSJSONSerialization (和 JSONSerialization 在Swift中)遵循一般模式:

  1. 如果数字只有一个整数部分(没有小数或指数),请尝试将其解析为a long long。如果没有溢出,请返回 NSNumber 同 long long
  2. 尝试用双解析 strtod_l。如果它没有溢出,返回一个 NSNumber 同 double
  3. 在所有其他情况下,尝试使用 NSDecimalNumber 它支持更大范围的值,特别是最多38位的尾数和-128 ... 127之间的指数。

如果您查看人们发布的其他示例,您可以看到当值超出范围或精度时 double 你得到一个 NSDecimalNumber 背部。


6
2017-09-18 02:58





简短的回答是,如果需要NSDecimalNumber级别的精度,则不应序列化为JSON。 JSON只有一种数字格式:double,它的精度低于NSDecimalNumber。

只有学术兴趣的长答案,因为简短答案也是正确的答案,是“不一定”。 NSJSONSerialization有时会反序列化为NSDecimalNumber,但它没有记录,我还没有确定它所处的环境是什么。例如:

    BOOL boolYes = YES;
    int16_t int16 = 12345;
    int32_t int32 = 2134567890;
    uint32_t uint32 = 3124141341;
    unsigned long long ull = 312414134131241413ull;
    double dlrep = 1.5;
    double dlmayrep = 1.1234567891011127;
    float fl = 3124134134678.13;
    double dl = 13421331.72348729 * 1000000000000000000000000000000000000000000000000000.0;
    long long negLong = -632414314135135234;
    unsigned long long unrepresentable = 10765432100123456789ull;

    dict[@"bool"] = @(boolYes);
    dict[@"int16"] = @(int16);
    dict[@"int32"] = @(int32);
    dict[@"dlrep"] = @(dlrep);
    dict[@"dlmayrep"] = @(dlmayrep);
    dict[@"fl"] = @(fl);
    dict[@"dl"] = @(dl);
    dict[@"uint32"] = @(uint32);
    dict[@"ull"] = @(ull);
    dict[@"negLong"] = @(negLong);
    dict[@"unrepresentable"] = @(unrepresentable);

    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];

    NSDictionary *dict_back = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

并在调试器中:

(lldb) po [dict_back[@"bool"] class]
__NSCFBoolean
(lldb) po [dict_back[@"int16"] class]
__NSCFNumber
(lldb) po [dict_back[@"int32"] class]
__NSCFNumber
(lldb) po [dict_back[@"ull"] class]
__NSCFNumber
(lldb) po [dict_back[@"fl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dlrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"dlmayrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"negLong"] class]
__NSCFNumber
(lldb) po [dict_back[@"unrepresentable"] class]
NSDecimalNumber

所以你要做的就是这样。你绝对不应该假设如果你将NSDecimalNumber序列化为JSON,你将得到一个NSDecimalNumber。

但是,再次,您不应该将NSDecimalNumbers存储在JSON中。


3
2018-01-27 16:27



这是不正确的。 JSON格式不关心浮点数或双精度。这是失去精度的JSON解析器。 - Sulthan
@icodestuff:如果我使用你的代码运行 double dl = 8.92918e-128;, dict_back 是 nil。你有没有想过是否有办法让JSON反序列化使用这些值 NSJSONSerialization 还是其他图书馆? - sergio
正如Sulthan所说,JSON并不关心精度。如果Apple的API遵循规范,则应使用NSDecimalNumber。 - xtravar
如果你检查我的评论,它确实使用 NSDecimalNumber 它支持更大的范围但不同的精度特性。 - russbishop


要回答标题中的问题:不,它没有,它创造了 NSNumber 对象。你可以轻松测试这个:

NSArray *a = @[[NSDecimalNumber decimalNumberWithString:@"0.1"]];
NSData *data = [NSJSONSerialization dataWithJSONObject:a options:0 error:NULL];
a = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@", [a[0] class]);

将打印 __NSCFNumber

你可以转换它 NSNumber 反对 NSDecimalNumber 同 [NSDecimalNumber decimalNumberWithDecimal:[number decimalValue]],但根据文件 decimalValue

对于float和double值,返回的值不保证是精确的。


1
2017-08-01 06:43



NSDecimalNumber是NSNumber的子类。如果你使用的值不适合double,那么是的,它会创建一个NSDecimalNumber。 - fishinear


我有同样的问题,除了我使用Swift 3.我做了 JSONSerialization类的修补版本 将所有数字解析为 Decimal的。它只能解析/反序列化JSON,但没有任何序列化代码。它基于Apple在Swift中重新实现Foundation的开源。


1
2018-02-26 18:10