JSON ha lasciato fuori Infinity e NaN; Stato JSON in ECMAScript?


180

Qualche idea sul perché JSON abbia escluso NaN e +/- Infinity? Mette Javascript nella strana situazione in cui gli oggetti che sarebbero altrimenti serializzabili non lo sono, se contengono valori di NaN o +/- infinito.

Sembra che questo sia stato lanciato in pietra: vedi RFC4627 ed ECMA-262 (sezione 24.5.2, JSON.stringify, NOTA 4, pagina 683 del pdf ECMA-262 all'ultima modifica):

I numeri finiti sono stringiti come se chiamando ToString(number). NaN e Infinity indipendentemente dal segno sono rappresentati come String null.


Non riesco a trovare quella citazione in nessuno dei due documenti.
wingedsubmariner,

1
risolto, sembra che ci fosse un riferimento stantio / modifica stantio in qualche modo.
Jason S,

Risposte:


90

Infinitye NaNnon sono parole chiave o niente di speciale, sono solo proprietà dell'oggetto globale (così com'è undefined) e come tali possono essere cambiate. È per questo motivo che JSON non li include nelle specifiche - in sostanza qualsiasi stringa JSON vera dovrebbe avere lo stesso risultato in EcmaScript se lo fai eval(jsonString)oJSON.parse(jsonString) .

Se fosse permesso, allora qualcuno potrebbe iniettare un codice simile a

NaN={valueOf:function(){ do evil }};
Infinity={valueOf:function(){ do evil }};

in un forum (o qualsiasi altra cosa) e quindi qualsiasi utilizzo json su quel sito potrebbe essere compromesso.


29
Se si valuta 1/0 si ottiene Infinity, se si valuta -1/0 si ottiene -Infinity, se si valuta 0/0 si ottiene NaN.
Jason S,

9
Ma i termini NaNe Infinitysono nomi di proprietà, quindi mentre String (1/0) produce una stringa "Infinity"che è solo la rappresentazione in stringa del valore infinito. Non è possibile rappresentare uno NaNo Infinitycome valori letterali è ES: è necessario utilizzare un'espressione (ad es. 1/0, 0/0 ecc.) O una ricerca di proprietà (riferendosi a Infinityo NaN). Dato che richiedono l'esecuzione di codice, non possono essere inclusi in JSON.
Olliej,

16
Per quanto riguarda la sicurezza, tutto ciò che un parser JSON decente dovrebbe fare quando va a convertire NaN è di produrre il valore 0/0 (piuttosto che valutare il simbolo NaN) che restituirà il NaN "reale" indipendentemente da cosa il simbolo NaN viene ridefinito come.
Jason S,

33
@olliej: pensi che NaN non sia letterale, non conosco abbastanza Javascript per giudicare la semantica javascript. Ma per un formato di file che memorizza numeri in virgola mobile a precisione doppia, dovrebbe esserci un modo per definire i float IEEE, cioè da un letterale NaN / Infinity / NegInfinity. Questi sono stati dei doppi a 64 bit e come tali dovrebbero essere rappresentabili. Ci sono persone che dipendono da loro (per motivi). Probabilmente sono stati dimenticati perché JSON / Javascript ha avuto origine nello sviluppo web anziché nel calcolo scientifico.
Wirrbel,

35
È al 100%, assolutamente SBAGLIATO che JSON abbia arbitrariamente omesso gli stati di numero in virgola mobile perfettamente validi e standard di NaN, Infinity e -Infinity. In sostanza, JSON ha deciso di supportare un sottoinsieme arbitrario di valori float IEEE, omettendo ignorando tre valori specifici perché sono difficili o qualcosa del genere. No. L'abilità di valutazione non è nemmeno una scusa, perché tali numeri avrebbero potuto essere codificati come i letterali 1/0, -1/0 e 0/0. Si tratterebbe di numeri validi aggiunti con "/ 0", che non è solo semplice da rilevare, ma effettivamente valutabile come ES allo stesso tempo. Niente scuse.
Triynko,

56

Sulla domanda originale: sono d'accordo con l'utente "cbare" in quanto questa è una sfortunata omissione in JSON. IEEE754 li definisce come tre valori speciali di un numero in virgola mobile. Quindi JSON non può rappresentare completamente i numeri in virgola mobile IEEE754. In realtà è anche peggio, poiché JSON come definito in ECMA262 5.1 non definisce nemmeno se i suoi numeri sono basati su IEEE754. Poiché il flusso di progettazione descritto per la funzione stringify () in ECMA262 menziona i tre valori IEEE speciali, si può sospettare che l'intenzione fosse in realtà quella di supportare i numeri in virgola mobile IEEE754.

Come un altro punto dati, non correlato alla domanda: tipi di dati XML xs: float e xs: double indicano che sono basati su numeri in virgola mobile IEEE754 e supportano la rappresentazione di questi tre valori speciali (Vedi W3C XSD 1.0 Parte 2 , Tipi di dati).


5
Sono d'accordo che sia tutto sfortunato. Ma forse è una buona cosa che i numeri JSON non specifichino l'esatto formato in virgola mobile. Anche IEEE754 specifica molti formati: dimensioni diverse e una distinzione tra esponenti decimali e binari. JSON è particolarmente adatto ai decimali, quindi sarebbe un peccato se qualche standard lo fissasse in binario.
Adrian Ratnapala,

5
@AdrianRatnapala +1 In effetti: i numeri JSON hanno una precisione potenzialmente infinita, quindi sono molto meglio delle specifiche IEEE, poiché non hanno limiti di dimensioni, limiti di precisione e effetti di arrotondamento (se il serializzatore è in grado di gestirli).
Arnaud Bouchez,

2
@ArnaudBouchez. Detto questo, JSON dovrebbe comunque supportare stringhe che rappresentano NaN e + -Infinity. Anche se JSON non dovrebbe essere aggiunto a nessun formato IEEE, le persone che definiscono il formato numerico dovrebbero almeno guardare la pagina IEEE754 di Wikipedia e fermarsi un attimo a pensare.
Adrian Ratnapala,


Questo non è un peccato. Vedi la risposta di @CervEd. Non è legato a IEE754, il che è positivo (anche se la maggior parte dei linguaggi di programmazione utilizza IEEE754 e quindi richiede un'ulteriore elaborazione in caso di NaN, ecc.).
Ludovic Kuty il

16

Potresti adattare il modello di oggetti null e nel tuo JSON rappresentare valori come

"myNum" : {
   "isNaN" :false,
   "isInfinity" :true
}

Quindi, durante il controllo, è possibile verificare il tipo

if (typeof(myObj.myNum) == 'number') {/* do this */}
else if (myObj.myNum.isNaN) {/* do that*/}
else if (myObj.myNum.isInfinity) {/* Do another thing */}

So che in Java è possibile ignorare i metodi di serializzazione per implementare una cosa del genere. Non sono sicuro da dove provenga la serializzazione, quindi non posso fornire dettagli su come implementarlo nei metodi di serializzazione.


1
hmmm ... questa è una risposta a una soluzione alternativa; Non stavo davvero chiedendo una soluzione alternativa, ma piuttosto il motivo per cui questi valori erano esclusi. Ma +1 comunque.
Jason S,

2
@Zoidberg: undefinednon è una parola chiave, è una proprietà sull'oggetto globale
olliej

2
@Zoidberg: undefined è una proprietà sull'oggetto globale - non è una parola chiave, quindi "undefined" in thisrestituisce true nell'ambito globale. Significa anche che puoi fare undefined = 42e if (myVar == undefined)diventa (essenzialmente) myVar == 42. Ciò si ricollega ai primi giorni di ecmascript nee javascript dove undefinednon esistevano di default, quindi le persone lo facevano solo var undefinednell'ambito globale. Di conseguenza undefinednon è stato possibile creare una parola chiave senza rompere i siti esistenti e quindi siamo stati condannati per sempre a non avere definito una proprietà normale.
Olliej,

2
@olliej: non ho idea del perché pensi che indefinito sia una proprietà dell'oggetto globale. Per impostazione predefinita, la ricerca di undefined è il valore incorporato di undefined. Se lo sostituisci con "undefined = 42", quando accedi a indefinito come una ricerca variabile, ottieni il valore sovrascritto. Ma prova a fare "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined new =' + (xa === undefined)". Non puoi mai ridefinire i valori interni di null, indefinito, NaN o Infinity, anche se puoi sovrascrivere le loro ricerche di simboli.
Jason S,

2
@Jason undefinedè una proprietà globale perché è specificata come tale. Consultare il 15.1.1.3 di ECMAScript-262 3a ed.
Kangax,

11

Le stringhe "Infinity", "-Infinity" e "NaN" vengono forzate tutti ai valori previsti in JS. Quindi direi che il modo giusto di rappresentare questi valori in JSON è come stringhe.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

È un peccato che JSON.stringify non lo faccia per impostazione predefinita. Ma c'è un modo:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"

1
0/0, ecc., Non sono JSON validi. Devi lavorare entro i limiti dello standard e le stringhe fanno bene il lavoro.
teh_senaus,

Al contrario, penso che questa sia l'unica soluzione pratica, ma farò una funzione che restituirà NaN se il valore di input è "NaN", ecc. Il modo in cui si esegue la conversione è incline all'iniezione di codice.
Marco Sulla

3
I valori JSON non possono essere espressioni aritmetiche ... l'obiettivo di rendere lo standard separato dalla sintassi letterale del linguaggio è rendere deeserializzabile JSON senza eseguirlo come codice. Non so perché non avremmo potuto NaNe Infinityaggiunto come valori di parole chiave come truee false, comunque.
Mark Reed,

Per rendere più esplicito, si può usare Number("Infinity"), Number("-Infinity")eNumber("NaN")
HKTonyLee

Questo è un lavoro magico. JSON.parse("{ \"value\" : -1e99999 }")tornare facilmente { value:-Infinity }in javascript. Solo che non è compatibile con il tipo di numero personalizzato che potrebbe essere più grande di quello
Thaina,

7

Se si ha accesso al codice di serializzazione, è possibile rappresentare Infinity come 1.0e + 1024. L'esponente è troppo grande per rappresentare in doppio e quando deserializzato viene rappresentato come Infinito. Funziona su webkit, incerto su altri parser json!


4
IEEE754 supporta numeri a virgola mobile a 128 bit, quindi 1.0e5000 è meglio
Ton Plomp

2
Ton: 128 bit è stato aggiunto in seguito. E se decidessero di aggiungere 256 bit? Quindi dovrai aggiungere più zeri e il codice esistente si comporterà in modo diverso. Infinitylo sarà sempre Infinity, quindi perché non supportarlo?
pecora volante

1
Idea intelligente! Stavo per passare a un formato diverso o aggiungere un ingombrante codice di soluzione al mio parser. Non è l'ideale per ogni caso, ma nel mio caso, dove l'infinito funge solo da custodia ottimizzata per il bordo di una sequenza convergente, è semplicemente perfetto e anche se si introdurrebbe una maggiore precisione sarebbe comunque per lo più corretto. Grazie!
O Sharir,

3
1, -1 e 0 ..... numeri perfettamente validi / analizzabili, diventano quei tre valori speciali quando li aggiungi semplicemente /0alla fine. È facilmente analizzabile, immediatamente visibile e persino valutabile. È ingiustificabile che non lo abbiano ancora aggiunto allo standard: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << Perché no? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Niente scuse.
Triynko,


1

L'attuale IEEE Std 754-2008 include definizioni per due diverse rappresentazioni in virgola mobile a 64 bit: un tipo decimale in virgola mobile a 64 bit e un binario in virgola mobile a 64 bit.

Dopo aver arrotondato la stringa .99999990000000006è la stessa .9999999della rappresentazione binaria IEEE a 64 bit ma NON è la stessa .9999999della rappresentazione decimale IEEE a 64 bit. In virgola mobile decimale IEEE a 64 bit.99999990000000006 arrotonda al valore .9999999000000001che non è uguale al decimale.9999999 valore .

Poiché JSON considera i valori numerici solo come stringhe numeriche di cifre decimali, non esiste alcun modo per un sistema che supporti rappresentazioni in virgola mobile e decimali IEEE (come IBM Power) per determinare quale dei due possibili valori in virgola mobile IEEE è previsto.


Cosa c'entra questo con la domanda? (che parla di Infinity e NaN)
Bryan

1

Potenziale soluzione per casi come {"chiave": Infinito}:

JSON.parse(theString.replace(/":(Infinity|-IsNaN)/g, '":"{{$1}}"'), function(k, v) {
   if (v === '{{Infinity}}') return Infinity;
   else if (v === '{{-Infinity}}') return -Infinity;
   else if (v === '{{NaN}}') return NaN;
   return v;
   });

L'idea generale è quella di sostituire le occorrenze di valori non validi con una stringa che riconosceremo durante l'analisi e sostituirla con la rappresentazione JavaScript appropriata.


Non so perché questa soluzione abbia ottenuto un downvote perché, francamente, se ti trovi in ​​una situazione in cui la tua stringa JSON contiene valori Infinity o IsNaN, fallirà quando proverai ad analizzarla. Usando questa tecnica, devi prima sostituire le occorrenze di IsNaN o Infinity con qualcos'altro (per isolarle da qualsiasi stringa valida che potrebbe contenere quei termini) e utilizzare JSON.parse (stringa, callback) per restituire i valori JavaScript corretti e validi. Sto usando questo nel codice di produzione e non ho mai avuto problemi.
SHamel

Questo non rovinerebbe l'Infinito nelle corde? Per molti casi d'uso è sicuro supporre che non sia un problema, ma la soluzione non è completamente solida.
olejorgenb,

1

Il motivo è indicato a pagina ii in Standard ECMA-404 La sintassi di interscambio di dati JSON, 1a edizione

JSON è agnostico riguardo ai numeri. In qualsiasi linguaggio di programmazione, ci possono essere una varietà di tipi numerici di varie capacità e complementi, fissi o mobili, binari o decimali. Ciò può rendere difficile l'interscambio tra diversi linguaggi di programmazione. JSON invece offre solo la rappresentazione di numeri che gli umani usano: una sequenza di cifre. Tutti i linguaggi di programmazione sanno come dare un senso alle sequenze di cifre anche se non sono d'accordo sulle rappresentazioni interne. Questo è sufficiente per consentire l'interscambio.

Il motivo non è, come molti hanno affermato, a causa delle rappresentazioni NaNe della Infinitysceneggiatura dell'ECMA. La semplicità è un principio progettuale fondamentale di JSON.

Poiché è così semplice, non si prevede che la grammatica JSON cambierà mai. Ciò conferisce a JSON, come notazione di base, un'enorme stabilità


-3

Se come me non hai alcun controllo sul codice di serializzazione, puoi gestire i valori NaN sostituendoli con null o qualsiasi altro valore come un po 'di un hack come segue:

$.get("file.json", theCallback)
.fail(function(data) {
  theCallback(JSON.parse(data.responseText.replace(/NaN/g,'null'))); 
} );

In sostanza, .fail verrà chiamato quando il parser json originale rileva un token non valido. Quindi viene utilizzata una sostituzione stringa per sostituire i token non validi. Nel mio caso è un'eccezione per il serializzatore restituire valori NaN, quindi questo metodo è l'approccio migliore. Se i risultati normalmente contengono un token non valido, sarebbe meglio non usare $ .get ma invece recuperare manualmente il risultato JSON ed eseguire sempre la sostituzione della stringa.


21
Intelligente, ma non del tutto infallibile. Provalo con{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ

1
e devi usare jQuery. Non ho $ .get ().
Jason S
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.