2013-08-01 12 views
11

Prendere il seguente pezzo di codice:NSJSONSerialization deserializza i numeri come NSDecimalNumber?

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

La mia domanda è, è un results[@"foo"] NSDecimalNumber, o qualcosa con precisione finita binario come un doppio o galleggiante? Fondamentalmente, ho un'applicazione che richiede l'accuratezza senza perdita che viene fornita con uno NSDecimalNumber, e ho bisogno di assicurare che la deserializzazione JSON non comporti arrotondamenti a causa di double/float eccetera.

E.g. se è stato interpretato come un galleggiante, mi piacerebbe correre in problemi come questo con precisione:

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

ho provato interpretare foo come NSDecimalNumber e stampare il risultato:

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

Ma poi ho ha trovato che chiamare stringValue su un NSDecimalNumber non stampa tutte le cifre significative in ogni caso, per esempio ..

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

... così la stampa 012.333.199.098.non mi dice se lo results[@"foo"] sia stato arrotondato a un certo punto alla precisione finita dal parser JSON o meno.

essere chiaro, realizzo ho potuto utilizzare una stringa piuttosto che un numero nella rappresentazione JSON per memorizzare il valore di foo, cioè "0.1" anziché 0.1, e quindi utilizzare [NSDecimalNumber decimalNumberWithString:results[@"foo"]]. Ma quello che mi interessa è come la classe NSJSONSerialization deserializzi i numeri JSON, quindi so se questo è veramente necessario o no.

risposta

2

Per rispondere alla domanda nel titolo: No, non crea oggetti NSNumber. È possibile verificare facilmente questo:

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]); 

stampa __NSCFNumber.

è possibile convertire che NSNumber oggetto in un NSDecimalNumber con [NSDecimalNumber decimalNumberWithDecimal:[number decimalValue]], ma secondo la documentazione per decimalValue

Il valore restituito non è sempre garantito esatta per float e valori doppi.

3

La risposta breve è che non è necessario serializzare su JSON se si richiedono livelli di precisione NSDecimalNumber. JSON ha un solo formato numerico: double, che ha una precisione inferiore a NSDecimalNumber.

La risposta lunga, che è solo di interesse accademico, perché la risposta breve è anche la risposta giusta, è "Non necessariamente". NSJSONSerialization talvolta deserializza come NSDecimalNumber, ma non è documentato e non ho determinato quale sia l'insieme di circostanze in cui viene eseguito. Per esempio:

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 =ull; 

    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]; 

e nel debugger:

(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 

Quindi, fare di che cosa si vuole.Non si deve assolutamente presumere che se si serializza un NSDecimalNumber su JSON si otterrà un NSDecimalNumber di nuovo.

Ma, ancora, non è necessario memorizzare NSDecimalNumbers in JSON.

+5

Questo non è corretto. Il formato JSON non si preoccupa del float o della doppia precisione. È il parser JSON che perde la precisione. – Sulthan

+0

@icodestuff: se eseguo il codice usando 'double dl = 8.92918e-128;', 'dict_back' è' nil'. Hai idea se esiste un modo per far sì che la deserializzazione JSON funzioni con tali valori usando 'NSJSONSerialization' o altre librerie? – sergio

+0

Come ha detto Sulthan, a JSON non interessa la precisione. Se l'API di Apple segue le specifiche, dovrebbe utilizzare NSDecimalNumber. – xtravar

4

NSJSONSerialization (e JSONSerialization in Swift) seguono lo schema generale:

  1. Se un numero ha solo una parte intera (senza decimale o esponente), tentare di analizzare come long long. In caso contrario, restituire un NSNumber con long long.
  2. Tentativo di analizzare un doppio con strtod_l. Se non eccede, restituire un NSNumber con double.
  3. In tutti gli altri casi, provare a utilizzare NSDecimalNumber che supporta un intervallo di valori molto più ampio, in particolare una mantissa fino a 38 cifre ed esponente compreso tra -128 ... 127.

Se si guarda in altri esempi di persone hanno postato si può vedere che quando il valore supera l'intervallo o la precisione di un double si ottiene un NSDecimalNumber indietro.

1

Ho avuto lo stesso problema, tranne che sto usando Swift 3. Ho fatto a patched version of the JSONSerialization class che analizza tutti i numeri come Decimal. Può solo analizzare/deserializzare JSON, ma non ha alcun codice di serializzazione. Si basa sulla re-implementazione open source di Apple della Foundation in Swift.

Problemi correlati