JSON omitió Infinity y NaN; ¿Estado JSON en ECMAScript?

180

¿Alguna idea de por qué JSON omitió NaN y +/- Infinity? Pone a Javascript en la extraña situación en la que los objetos que de otro modo serían serializables, no lo son, si contienen valores de NaN o +/- infinito.

Parece que esto ha sido moldeado: ver RFC4627 y ECMA-262 (sección 24.5.2, JSON.stringify, NOTA 4, página 683 del PDF de ECMA-262 en la última edición):

Los números finitos se stringifican como llamando ToString(number). NaN e Infinity independientemente del signo se representan como la Cadena null.

Jason S
fuente
No puedo encontrar esa cita en ninguno de los documentos.
wingedsubmariner
1
arreglado, parece que hubo una referencia obsoleta / edición obsoleta de alguna manera.
Jason S

Respuestas:

90

Infinityy NaNno son palabras clave ni nada especial, son solo propiedades en el objeto global (tal cual undefined) y, como tal, se pueden cambiar. Es por esa razón que JSON no los incluye en la especificación: en esencia, cualquier cadena JSON verdadera debería tener el mismo resultado en EcmaScript si lo hace eval(jsonString)o no JSON.parse(jsonString).

Si se permitiera, alguien podría inyectar un código similar a

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

en un foro (o lo que sea) y luego cualquier uso de json en ese sitio podría verse comprometido.

olliej
fuente
29
Si evalúa 1/0 obtiene Infinity, si evalúa -1/0 obtiene -Infinity, si evalúa 0/0 obtiene NaN.
Jason S
9
Pero los términos NaNy Infinityson nombres de propiedades, por lo tanto, mientras String (1/0) produce una cadena "Infinity"que es solo la representación de cadena del valor infinito. No es posible representar ya sea NaNo Infinitycomo valores literales es ES - ya sea que usted tiene que utilizar una expresión (por ejemplo, 1/0, 0/0, etc.) O una búsqueda de propiedad (en referencia a Infinity, o NaN). Como esos requieren ejecución de código, no pueden incluirse en JSON.
olliej
16
Según su punto de vista sobre seguridad, todo lo que un analizador JSON decente tendría que hacer cuando se trata de convertir NaN es producir el valor 0/0 (en lugar de evaluar el símbolo NaN) que devolverá el NaN "real" independientemente de qué el símbolo NaN se redefine como.
Jason S
33
@olliej: usted argumenta que NaN no es literal, no conozco Javascript lo suficiente como para juzgar la semántica de JavaScript. Pero para un formato de archivo que almacena números de coma flotante de doble precisión, debería haber una forma de definir flotantes IEEE, es decir, mediante un literal NaN / Infinity / NegInfinity. Estos son estados de los dobles de 64 bits y, como tales, deben ser representables. Hay personas que dependen de ellos (por razones). Probablemente se olvidaron porque JSON / Javascript se originó en el desarrollo web en lugar de la informática científica.
wirrbel
35
Es 100%, absolutamente INCORRECTO que JSON haya omitido arbitrariamente estados de números de punto flotante perfectamente válidos y estándar de NaN, Infinity e -Infinity. Esencialmente, JSON decidió admitir un subconjunto arbitrario de valores flotantes IEEE, omitiendo ignorantemente tres valores específicos porque son difíciles o algo así. No. La capacidad de evaluación ni siquiera es una excusa, porque dichos números podrían haberse codificado como los literales 1/0, -1/0 y 0/0. Serían números válidos añadidos con "/ 0", que no solo es fácil de detectar, sino que también se puede evaluar como ES al mismo tiempo. Sin excusas.
Triynko
56

Sobre la pregunta original: estoy de acuerdo con el usuario "cbare" en que esta es una omisión desafortunada en JSON. IEEE754 define estos como tres valores especiales de un número de coma flotante. Por lo tanto, JSON no puede representar completamente los números de coma flotante IEEE754. De hecho, es aún peor, ya que JSON como se define en ECMA262 5.1 ni siquiera define si sus números se basan en IEEE754. Dado que el flujo de diseño descrito para la función stringify () en ECMA262 menciona los tres valores IEEE especiales, se puede sospechar que la intención era de hecho admitir números de punto flotante IEEE754.

Como otro punto de datos, no relacionado con la pregunta: tipos de datos XML xs: float y xs: double afirman que están basados ​​en números de punto flotante IEEE754, y que admiten la representación de estos tres valores especiales (Ver W3C XSD 1.0 Parte 2 , Tipos de datos).

Andreas Maier
fuente
55
Estoy de acuerdo en que todo esto es lamentable. Pero quizás sea bueno que los números JSON no especifiquen el formato exacto de coma flotante. Incluso IEEE754 especifica muchos formatos: diferentes tamaños y una distinción entre exponentes decimales y binarios. JSON es particularmente adecuado para el decimal, por lo que sería una pena que algún estándar lo fijara en binario.
Adrian Ratnapala
55
@AdrianRatnapala +1 De hecho: los números JSON tienen una precisión potencialmente infinita, por lo que son mucho mejores que las especificaciones IEEE, ya que no tienen límite de tamaño, límite de precisión ni efecto de redondeo (si el serializador puede manejarlo).
Arnaud Bouchez
2
@ArnaudBouchez. Dicho esto, JSON aún debe admitir cadenas que representen NaN y + -Infinity. Incluso si JSON no se debe anclar a ningún formato IEEE, las personas que definen el formato de número deberían al menos mirar la página de Wikipedia IEEE754 y detenerse un momento para pensar.
Adrian Ratnapala
Esto no es desafortunado. Ver la respuesta de @CervEd. No está vinculado a IEE754, lo cual es algo bueno (incluso si la mayoría de los lenguajes de programación usan IEEE754 y, por lo tanto, requiere un procesamiento adicional en caso de NaN, etc.).
Ludovic Kuty
16

¿Podría adaptar el patrón de objeto nulo y en su JSON representar valores como

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

Luego, al verificar, puede verificar el tipo

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

Sé que en Java puedes anular los métodos de serialización para implementar tal cosa. No estoy seguro de dónde está su serialización, por lo que no puedo dar detalles sobre cómo implementarlo en los métodos de serialización.

Zoidberg
fuente
1
hmmm ... esa es una respuesta a una solución alternativa; Realmente no estaba pidiendo una solución, sino más bien por qué estos valores eran excluyentes. Pero +1 de todos modos.
Jason S
2
@Zoidberg: undefinedno es una palabra clave, es una propiedad del objeto global
olliej
2
@Zoidberg: undefined es una propiedad del objeto global; no es una palabra clave, por lo que "undefined" in thisdevuelve verdadero en el ámbito global. También significa que puedes hacer undefined = 42y se if (myVar == undefined)convierte (esencialmente) myVar == 42. Esto se remonta a los primeros días de ecmascript nee javascript, donde undefinedno existía de forma predeterminada, por lo que las personas simplemente lo hicieron var undefineden el ámbito global. En consecuencia undefined, no se podría convertir en una palabra clave sin romper los sitios existentes, por lo que estábamos condenados a ser indefinidos una propiedad normal.
olliej
2
@olliej: No tengo idea de por qué crees que indefinido es una propiedad del objeto global. Por defecto, la búsqueda de undefined es el valor incorporado de undefined. Si lo anula con "undefined = 42", cuando accede a undefined como una búsqueda de variable, obtiene el valor anulado. Pero intente hacer "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined new =' + (xa === undefined)". Nunca puede redefinir los valores internos de nulo, indefinido, NaN o Infinito, incluso si puede anular sus búsquedas de símbolos.
Jason S
2
@ Jason undefinedes una propiedad global porque se especifica como tal. Consulte 15.1.1.3 de ECMAScript-262 3rd ed.
kangax
11

Las cadenas "Infinity", "-Infinity" y "NaN" obligan a los valores esperados en JS. Entonces argumentaría que la forma correcta de representar estos valores en JSON es como cadenas.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

Es solo una lástima que JSON.stringify no lo haga por defecto. Pero, hay una manera:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"
teh_senaus
fuente
1
0/0, etc., no son JSON válidos. Debe trabajar dentro de los límites del estándar, y las cadenas hacen el trabajo bien.
teh_senaus
Por el contrario, creo que esta es la única solución práctica, pero haré una función que devuelva NaN si el valor de entrada es "NaN", etc. La forma en que realiza la conversión es propensa a la inyección de código.
Marco Sulla
3
Los valores JSON no pueden ser expresiones aritméticas ... el objetivo de hacer que el estándar se separe de la sintaxis literal del lenguaje es hacer que JSON sea deserializable sin ejecutar nada como código. Sin embargo, no estoy seguro de por qué no pudimos tener NaNy Infinityagregar como valores de palabras clave como truey false.
Mark Reed
Para hacerlo más explícito, podemos usar Number("Infinity"), Number("-Infinity")yNumber("NaN")
HKTonyLee
Esto es trabajo como magia. JSON.parse("{ \"value\" : -1e99999 }")regresar fácilmente { value:-Infinity }en javascript. Solo que no es compatible con el tipo de número personalizado que podría ser más grande que eso
Thaina
7

Si tiene acceso al código de serialización, puede representar Infinity como 1.0e + 1024. El exponente es demasiado grande para representarlo en un doble y, cuando se deserializa, se representa como Infinito. Funciona en webkit, ¡no estoy seguro acerca de otros analizadores json!

kuwerty
fuente
44
IEEE754 admite números de coma flotante de 128 bits, por lo que 1.0e5000 es mejor
Ton Plomp
2
Ton: 128 bits se agregó más tarde. ¿Qué pasa si deciden agregar 256 bits? Luego tendrá que agregar más ceros, y el código existente se comportará de manera diferente. Infinitysiempre será Infinityasí que, ¿por qué no apoyar eso?
ovejas voladoras
1
¡Idea inteligente! Estaba a punto de cambiar a un formato diferente o agregar un código de solución engorroso a mi analizador. No es ideal para todos los casos, pero en mi caso, donde el infinito sirve solo como un caso de borde optimizado para una secuencia convergente, es perfecto e incluso si se introdujera una mayor precisión, aún sería principalmente correcto. ¡Gracias!
O Sharir
3
1, -1 y 0 ..... números perfectamente válidos / analizables, se convierten en esos tres valores especiales cuando simplemente suma /0al final de ellos. Es fácilmente analizable, inmediatamente visible e incluso evaluable. Es inexcusable que aún no lo hayan agregado al estándar: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << ¿Por qué no? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Sin excusas.
Triynko
1

El IEEE Std 754-2008 actual incluye definiciones para dos representaciones diferentes de punto flotante de 64 bits: un tipo decimal de punto flotante de 64 bits y un tipo binario de punto flotante de 64 bits.

Después de redondear la cadena .99999990000000006es la misma que .9999999en la representación binaria IEEE de 64 bits, pero NO es la misma que .9999999en la representación decimal IEEE de 64 bits. En IEEE de 64 bits, el punto flotante decimal se .99999990000000006redondea al valor .9999999000000001que no es el mismo que el .9999999valor decimal .

Dado que JSON solo trata los valores numéricos como cadenas numéricas de dígitos decimales, no hay forma de que un sistema que admita representaciones de punto flotante binario y decimal IEEE (como IBM Power) determine cuál de los dos posibles valores de punto flotante numérico IEEE es destinado a.

Steven Hobbs
fuente
¿Qué tiene esto que ver con la pregunta? (que trata sobre Infinity y NaN)
Bryan
1

Posible solución para casos como {"key": Infinity}:

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

La idea general es reemplazar las ocurrencias de valores no válidos con una cadena que reconoceremos al analizar y reemplazarla nuevamente con la representación JavaScript adecuada.

Shamel
fuente
No sé por qué esta solución recibió un voto negativo porque, francamente, si enfrenta una situación en la que su cadena JSON contiene valores Infinity o IsNaN, fallará cuando intente analizarla. Usando esta técnica, primero reemplaza las ocurrencias de IsNaN o Infinity con algo más (para aislarlas de cualquier cadena válida que pueda contener esos términos), y use el JSON.parse (cadena, devolución de llamada) para devolver los valores de JavaScript válidos y correctos. Estoy usando esto en el código de producción y nunca tuve ningún problema.
SHamel
¿No arruinaría esto Infinity dentro de las cuerdas? Para muchos casos de uso, probablemente sea seguro asumir que no es un problema, pero la solución no es completamente sólida.
olejorgenb
1

El motivo se indica en la página ii del Estándar ECMA-404 La sintaxis de intercambio de datos JSON, primera edición

JSON es agnóstico acerca de los números. En cualquier lenguaje de programación, puede haber una variedad de tipos de números de varias capacidades y complementos, fijos o flotantes, binarios o decimales. Eso puede dificultar el intercambio entre diferentes lenguajes de programación. En cambio, JSON ofrece solo la representación de números que usan los humanos: una secuencia de dígitos. Todos los lenguajes de programación saben cómo dar sentido a las secuencias de dígitos, incluso si no están de acuerdo con las representaciones internas. Eso es suficiente para permitir el intercambio.

La razón no es, como muchos han afirmado, debido a las representaciones de NaNy Infinityla escritura ECMA. La simplicidad es un principio de diseño central de JSON.

Debido a que es tan simple, no se espera que la gramática JSON cambie alguna vez. Esto le da a JSON, como notación fundamental, una tremenda estabilidad

CervEd
fuente
-3

Si, como yo, no tiene control sobre el código de serialización, puede lidiar con los valores de NaN reemplazándolos por nulos o cualquier otro valor como un truco de la siguiente manera:

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

En esencia, se llamará a .fail cuando el analizador json original detecte un token no válido. Luego se usa un reemplazo de cadena para reemplazar los tokens no válidos. En mi caso, es una excepción que el serializador devuelva valores de NaN, por lo que este método es el mejor enfoque. Si los resultados normalmente contienen un token no válido, sería mejor no usar $ .get, sino recuperar manualmente el resultado JSON y ejecutar siempre el reemplazo de la cadena.

usuario1478002
fuente
21
Inteligente, pero no completamente infalible. Pruébelo con{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ
1
y debes estar usando jQuery. No tengo $ .get ().
Jason S