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.