JSON deixou de fora Infinity e NaN; Status JSON no ECMAScript?

180

Alguma idéia de por que o JSON deixou de fora o NaN e o +/- Infinity? Ele coloca o Javascript na estranha situação em que os objetos que seriam serializáveis ​​não são, se eles contêm NaN ou +/- valores infinitos.

Parece que isso foi feito em pedra: consulte RFC4627 e ECMA-262 (seção 24.5.2, JSON.stringify, NOTA 4, página 683 do ECMA-262 em última edição):

Os números finitos são especificados como se fossem chamados ToString(number). NaN e Infinity, independentemente do sinal, são representados como a String null.

Jason S
fonte
Não consigo encontrar essa citação nos dois documentos.
precisa saber é o seguinte
1
corrigido, parece que havia uma referência obsoleta / edição obsoleta de alguma forma.
Jason S

Respostas:

90

Infinitye NaNnão são palavras-chave ou nada de especial, são apenas propriedades no objeto global (como estão undefined) e, como tal, podem ser alteradas. É por esse motivo que o JSON não os inclui na especificação - em essência, qualquer string JSON verdadeira deve ter o mesmo resultado no EcmaScript se você tiver eval(jsonString)ouJSON.parse(jsonString) .

Se fosse permitido, alguém poderia injetar código semelhante ao

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

em um fórum (ou o que seja) e, em seguida, qualquer uso de json nesse site poderá ser comprometido.

olliej
fonte
29
Se você avaliar 1/0, obterá o Infinito, se avaliar -1/0, obterá -Infinity, se avaliar 0/0, obterá o NaN.
Jason S
9
Mas os termos NaNe Infinitysão nomes de propriedades, portanto, enquanto String (1/0) produz uma string "Infinity"que é apenas a representação da string do valor infinito. Não é possível representar um NaNou Infinitycomo os valores literais são ES - você precisa usar uma expressão (por exemplo, 1/0, 0/0 etc) ou uma pesquisa de propriedade (consultando Infinityou NaN). Como esses requerem execução de código, eles não podem ser incluídos no JSON.
olliej
16
Para o seu ponto de segurança, tudo que um analisador JSON decente teria que fazer quando converter o NaN é gerar o valor 0/0 (em vez de avaliar o símbolo NaN), que retornará o NaN "real", independentemente do que o símbolo NaN é redefinido como.
21713 Jason S
33
@olliej: você argumenta que o NaN não é literal, não sei o Javascript o suficiente para julgar a semântica do javascript. Porém, para um formato de arquivo que armazene números de ponto flutuante de precisão dupla, deve haver uma maneira de definir flutuadores IEEE, ou seja, através de literais NaN / Infinity / NegInfinity. Estes são estados dos duplos de 64 bits e, como tal, devem ser representáveis. Há pessoas que dependem deles (por razões). Provavelmente foram esquecidos porque o JSON / Javascript se originou no desenvolvimento da Web em vez da computação científica.
wirrbel
35
É 100%, absolutamente ERRADO para o JSON omitir arbitrariamente estados de número de ponto flutuante padrão e perfeitamente válidos de NaN, Infinity e -Infinity. Essencialmente, o JSON decidiu dar suporte a um subconjunto arbitrário de valores flutuantes do IEEE, omitindo ignoradamente três valores específicos, porque eles são difíceis ou algo assim. Não. A avaliação não é sequer uma desculpa, porque esses números poderiam ter sido codificados como os literais 1/0, -1/0 e 0/0. Eles seriam números válidos anexados com "/ 0", o que não é apenas simples de detectar, mas também pode ser avaliado como ES ao mesmo tempo. Sem desculpa.
Triynko 8/09/15
56

Sobre a questão original: concordo com o usuário "cbare", pois é uma omissão infeliz no JSON. IEEE754 define esses três valores especiais de um número de ponto flutuante. Portanto, o JSON não pode representar totalmente os números de ponto flutuante IEEE754. Na verdade, é ainda pior, pois o JSON, conforme definido no ECMA262 5.1, nem mesmo define se seus números são baseados no IEEE754. Como o fluxo de projeto descrito para a função stringify () no ECMA262 menciona os três valores IEEE especiais, pode-se suspeitar que a intenção era de fato suportar números de ponto flutuante IEEE754.

Como outro ponto de dados, não relacionado à pergunta: tipos de dados XML xs: float e xs: double afirmam que são baseados nos números de ponto flutuante IEEE754 e suportam a representação desses três valores especiais (consulte W3C XSD 1.0 Parte 2 , Tipos de dados).

Andreas Maier
fonte
5
Concordo que tudo isso é lamentável. Mas talvez seja bom que os números JSON não especifiquem o formato exato de ponto flutuante. Até o IEEE754 especifica muitos formatos - tamanhos diferentes e uma distinção entre expoentes decimais e binários. O JSON é particularmente adequado ao decimal, portanto, seria uma pena se algum padrão o fixasse no binário.
Adrian Ratnapala
5
@AdrianRatnapala +1 De fato: os números JSON têm precisão potencialmente infinita, portanto, são muito melhores que as especificações IEEE, pois não têm limite de tamanho, limite de precisão e efeito de arredondamento (se o serializador puder lidar com isso).
Arnaud Bouchez
2
@ArnaudBouchez. Dito isto, o JSON ainda deve suportar cadeias que representam NaN e + -Infinity. Mesmo que o JSON não deva ser fixado em nenhum formato IEEE, as pessoas que definem o formato numérico devem pelo menos olhar para a página da wikipedia IEEE754 e parar um pouco para pensar.
Adrian Ratnapala
Isso não é lamentável. Veja a resposta de @CervEd. Ele não está vinculado ao IEE754, o que é uma coisa boa (mesmo que a maioria das linguagens de programação use o IEEE754 e, portanto, exija processamento adicional no caso de NaN, etc.).
Ludovic Kuty 10/03
16

Você poderia adaptar o padrão de objeto nulo e, em seu JSON, representar valores como

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

Então, ao verificar, você pode verificar o tipo

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

Eu sei que em Java você pode substituir os métodos de serialização para implementar uma coisa dessas. Não sei de onde você está serializando, então não posso fornecer detalhes sobre como implementá-lo nos métodos de serialização.

Zoidberg
fonte
1
hmmm ... isso é uma resposta para uma solução alternativa; Eu realmente não estava pedindo uma solução alternativa, mas por que esses valores estavam excluindo. Mas +1 de qualquer maneira.
21713 Jason S
2
@Zoidberg: undefinednão é uma palavra-chave, é uma propriedade do objeto global
olliej
2
@Zoidberg: undefined é uma propriedade no objeto global - não é uma palavra-chave, então "undefined" in thisretorna verdadeira no escopo global. Isso também significa que você pode fazer undefined = 42e if (myVar == undefined)se tornar (essencialmente) myVar == 42. Isso remonta aos primeiros dias do ecmascript nee javascript, onde undefinednão existia por padrão, então as pessoas simplesmente existiam var undefinedno escopo global. Consequentemente, undefinednão era possível criar uma palavra-chave sem quebrar os sites existentes e, por isso, estávamos condenados a não ser definidos como uma propriedade normal.
olliej
2
@olliej: Não faço ideia por que você acha que indefinido é uma propriedade no objeto global. Por padrão, a pesquisa de indefinido é o valor interno de indefinido. Se você substituí-lo por "undefined = 42", ao acessar undefined como uma pesquisa de variável, você obtém o valor substituído. Mas tente fazer "zz = indefinido; indefinido = 42; x = {}; 'indefinido antigo =' + (xa === zz) + ', indefinido novo =' + (xa === indefinido)". Você nunca pode redefinir os valores internos de nulo, indefinido, NaN ou Infinito, mesmo que possa substituir suas pesquisas de símbolo.
21713 Jason S
2
@Jason undefinedé uma propriedade global porque é especificada como tal. Consulte 15.1.1.3 do ECMAScript-262 3rd ed.
Kangax 18/09/09
11

As cadeias "Infinity", "-Infinity" e "NaN" são coerentes com os valores esperados em JS. Então, eu argumentaria que a maneira correta de representar esses valores no JSON é como strings.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

É uma pena que o JSON.stringify não faça isso por padrão. Mas, existe um jeito:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"
teh_senaus
fonte
1
0/0, etc, não são JSON válidos. Você tem que trabalhar dentro dos limites do padrão, e as cordas fazem o trabalho bem.
Teh_senaus 08/09/2015
Pelo contrário, acho que essa é a única solução prática, mas executarei uma função que retornará NaN se o valor de entrada for "NaN" etc. A maneira como você faz a conversão é propensa a injeção de código.
Marco Sulla
3
Os valores JSON não podem ser expressões aritméticas ... o objetivo de tornar o padrão separado da sintaxe literal da linguagem é tornar JSON deseserializável sem executar nada como código. Não sei por que não poderíamos ter NaNe Infinityadicionamos como valores de palavras-chave como truee false, no entanto.
Mark Reed
Para torná-lo mais explícito, podemos usar Number("Infinity"), Number("-Infinity")eNumber("NaN")
HKTonyLee
Isso é trabalho como mágica. JSON.parse("{ \"value\" : -1e99999 }")retornar facilmente { value:-Infinity }em javascript. Só que não é compatível com o tipo de número personalizado que pode ser maior que isso
Thaina
7

Se você tiver acesso ao código de serialização, poderá representar o Infinito como 1.0e + 1024. O expoente é muito grande para representar em dobro e, quando desserializado, é representado como Infinito. Funciona no webkit, inseguro sobre outros analisadores de json!

kuwerty
fonte
4
IEEE754 suporta números de ponto flutuante de 128 bits tão 1.0e5000 é melhor
Ton Plomp
2
Ton: 128 bits foi adicionado mais tarde. E se eles decidirem adicionar 256 bits? Você precisará adicionar mais zeros e o código existente se comportará de maneira diferente. Infinitysempre será Infinity, então por que não apoiar isso?
ovelha voadora
1
Ideia inteligente! Eu estava prestes a mudar para um formato diferente ou adicionar um código de solução alternativa complicado ao meu analisador. Não é ideal para todos os casos, mas no meu caso, onde o infinito serve apenas como um caso de borda otimizado para uma sequência convergente, é simplesmente perfeito e, mesmo que uma maior precisão fosse introduzida, ainda estaria na maior parte correta. Obrigado!
Ou Sharir
3
1, -1 e 0 ..... números perfeitamente válidos / analisáveis, tornam-se esses três valores especiais quando você simplesmente adiciona /0ao final deles. É facilmente analisável, imediatamente visível e até avaliável. É indesculpável que eles ainda não o tenham adicionado ao padrão: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << Por que não? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Sem desculpa.
Triynko
1

O atual IEEE Std 754-2008 inclui definições para duas representações de ponto flutuante de 64 bits diferentes: um tipo de ponto flutuante decimal de 64 bits e um tipo de ponto flutuante binário de 64 bits.

Após o arredondamento, a sequência .99999990000000006é a mesma que .9999999na representação binária de 64 bits do IEEE, mas NÃO é a mesma que .9999999na representação decimal de 64 bits do IEEE. No ponto IEEE decimal de 64 bits, o ponto flutuante .99999990000000006arredonda para o valor .9999999000000001que não é igual ao .9999999valor decimal .

Como o JSON trata apenas valores numéricos como sequências numéricas de dígitos decimais, não há como um sistema que suporte representações de ponto flutuante decimal e binário IEEE (como IBM Power) determinar qual dos dois possíveis valores numéricos de ponto flutuante IEEE é pretendido.

Steven Hobbs
fonte
O que isso tem a ver com a questão? (que é de cerca de Infinito e NaN)
Bryan
1

Solução alternativa em potencial 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;
   });

A idéia geral é substituir ocorrências de valores inválidos por uma sequência que reconheceremos ao analisar e substituí-la novamente pela representação JavaScript apropriada.

SHamel
fonte
Não sei por que essa solução teve um voto negativo porque, francamente, se você enfrentar uma situação em que sua cadeia JSON contenha valores Infinito ou IsNaN, ela falhará quando você tentar analisá-la. Usando essa técnica, primeiro substitua as ocorrências de IsNaN ou Infinity por outra (para isolá-las de qualquer sequência válida que possa conter esses termos) e use o JSON.parse (sequência, retorno de chamada) para retornar os valores válidos e válidos do JavaScript. Estou usando isso no código de produção e nunca tive nenhum problema.
shamel
Isso não bagunçaria o Infinito dentro de cadeias? Para muitos casos de uso, provavelmente é seguro assumir que não é um problema, mas a solução não é totalmente robusta.
olejorgenb
1

O motivo está indicado na página ii no Padrão ECMA-404 A Sintaxe de Intercâmbio de Dados JSON, 1ª Edição

JSON é independente de números. Em qualquer linguagem de programação, pode haver vários tipos de números de várias capacidades e complementos, fixos ou flutuantes, binários ou decimais. Isso pode dificultar o intercâmbio entre diferentes linguagens de programação. Em vez disso, o JSON oferece apenas a representação dos números que os humanos usam: uma sequência de dígitos. Todas as linguagens de programação sabem entender as seqüências de dígitos, mesmo que discordem das representações internas. Isso é suficiente para permitir o intercâmbio.

O motivo não é, como muitos afirmaram, devido às representações NaNe ao Infinityscript ECMA. A simplicidade é um princípio básico de design do JSON.

Por ser tão simples, não é esperado que a gramática JSON mude. Isso dá ao JSON, como notação fundamental, uma tremenda estabilidade

CervEd
fonte
-3

Se, como eu, você não tiver controle sobre o código de serialização, poderá lidar com os valores de NaN, substituindo-os por nulos ou qualquer outro valor como um hack, como segue:

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

Em essência, .fail será chamado quando o analisador json original detectar um token inválido. Em seguida, uma sequência de substituição é usada para substituir os tokens inválidos. No meu caso, é uma exceção o serializador retornar valores NaN, portanto esse método é a melhor abordagem. Se os resultados normalmente contiverem token inválido, é melhor não usar $ .get, mas recuperar manualmente o resultado JSON e sempre executar a substituição da cadeia.

user1478002
fonte
21
Inteligente, mas não totalmente infalível. Tente com{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ 04/04
1
e você deve estar usando jQuery. Eu não tenho $ .get ().
Jason S