JSON a omis Infinity et NaN; Statut JSON dans ECMAScript?

180

Une idée pourquoi JSON a omis NaN et +/- Infinity? Cela met Javascript dans la situation étrange où les objets qui seraient autrement sérialisables, ne le sont pas, s'ils contiennent des valeurs NaN ou +/- infini.

On dirait que cela a été coulé dans la pierre: voir RFC4627 et ECMA-262 (section 24.5.2, JSON.stringify, NOTE 4, page 683 du pdf ECMA-262 lors de la dernière édition):

Les nombres finis sont stringifiés comme en appelant ToString(number). NaN et Infinity, quel que soit le signe, sont représentés par la chaîne null.

Jason S
la source
Je ne trouve cette citation dans aucun des deux documents.
wingedsubmariner
1
corrigé, on dirait qu'il y avait une référence périmée / une modification périmée en quelque sorte.
Jason S

Réponses:

90

Infinityet NaNne sont pas des mots clés ou quoi que ce soit de spécial, ce ne sont que des propriétés sur l'objet global (tel quel undefined) et peuvent donc être modifiées. C'est pour cette raison que JSON ne les inclut pas dans la spécification - en substance, toute vraie chaîne JSON devrait avoir le même résultat dans EcmaScript si vous le faites eval(jsonString)ou JSON.parse(jsonString).

Si cela était autorisé, quelqu'un pourrait injecter du code semblable à

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

dans un forum (ou autre) et toute utilisation de json sur ce site pourrait être compromise.

olliej
la source
29
Si vous évaluez 1/0, vous obtenez Infinity, si vous évaluez -1/0, vous obtenez -Infinity, si vous évaluez 0/0, vous obtenez NaN.
Jason S
9
Mais les termes NaNet Infinitysont des noms de propriétés, alors que String (1/0) produit une chaîne "Infinity"qui n'est que la représentation sous forme de chaîne de la valeur infinity. Il n'est pas possible de représenter l'un NaNou l' autre ou Infinitycomme valeurs littérales est ES - vous devez soit utiliser une expression (par exemple 1/0, 0/0, etc.) ou une recherche de propriété (faisant référence à Infinityou NaN). Comme ceux-ci nécessitent l'exécution de code, ils ne peuvent pas être inclus dans JSON.
olliej
16
Pour ce qui est de la sûreté / sécurité, tout ce qu'un analyseur JSON décent aurait à faire lorsqu'il convertit NaN est de donner la valeur 0/0 (plutôt que d'évaluer le symbole NaN) qui renverra le "vrai" NaN indépendamment de ce le symbole NaN est redéfini comme.
Jason S
33
@olliej: vous soutenez que NaN n'est pas un littéral, je ne connais pas assez Javascript pour juger de la sémantique javascript. Mais pour un format de fichier qui stocke des nombres à virgule flottante double précision, il devrait y avoir un moyen de définir les flottants IEEE, c'est-à-dire par un littéral NaN / Infinity / NegInfinity. Ce sont des états des doubles 64 bits et devraient donc être représentables. Il y a des gens qui dépendent d'eux (pour des raisons). Ils ont probablement été oubliés car JSON / Javascript est né du développement Web au lieu de l'informatique scientifique.
wirrbel
35
C'est 100%, absolument FAUX pour JSON d'avoir omis arbitrairement des états de nombres à virgule flottante parfaitement valides et standard de NaN, Infinity et -Infinity. Essentiellement, JSON a décidé de prendre en charge un sous-ensemble arbitraire de valeurs flottantes IEEE, en omettant par ignorance trois valeurs spécifiques parce qu'elles sont difficiles ou quelque chose du genre. Non. La capacité d'évaluation n'est même pas une excuse, car de tels nombres auraient pu être codés comme les littéraux 1/0, -1/0 et 0/0. Il s'agirait de nombres valides accompagnés de "/ 0", ce qui est non seulement simple à détecter, mais en fait évaluable comme ES en même temps. Pas d'excuses.
Triynko
56

Sur la question d'origine: je suis d'accord avec l'utilisateur "cbare" en ce sens qu'il s'agit d'une omission malheureuse dans JSON. IEEE754 les définit comme trois valeurs spéciales d'un nombre à virgule flottante. Ainsi, JSON ne peut pas représenter entièrement les nombres à virgule flottante IEEE754. C'est en fait encore pire, puisque JSON tel que défini dans ECMA262 5.1 ne définit même pas si ses nombres sont basés sur IEEE754. Puisque le flux de conception décrit pour la fonction stringify () dans ECMA262 mentionne les trois valeurs spéciales IEEE, on peut soupçonner que l'intention était en fait de prendre en charge les nombres à virgule flottante IEEE754.

Comme un autre point de données, sans rapport avec la question: les types de données XML xs: float et xs: double indiquent qu'ils sont basés sur des nombres à virgule flottante IEEE754, et qu'ils prennent en charge la représentation de ces trois valeurs spéciales (voir W3C XSD 1.0 Partie 2 , Types de données).

Andreas Maier
la source
5
Je conviens que tout cela est malheureux. Mais c'est peut-être une bonne chose que les nombres JSON ne spécifient pas le format exact en virgule flottante. Même IEEE754 spécifie de nombreux formats - différentes tailles, et une distinction entre les exposants décimaux et binaires. JSON est particulièrement bien adapté au décimal, il serait donc dommage que certains standards l'épinglent en binaire.
Adrian Ratnapala
5
@AdrianRatnapala +1 En effet: les nombres JSON ont une précision potentiellement infinie, ils sont donc bien meilleurs que les spécifications IEEE, car ils n'ont pas de limite de taille, pas de limite de précision et pas d'effet d'arrondi (si le sérialiseur peut le gérer).
Arnaud Bouchez
2
@ArnaudBouchez. Cela dit, JSON devrait toujours prendre en charge les chaînes représentant NaN et + -Infinity. Même si JSON ne doit être épinglé à aucun format IEEE, les personnes qui définissent le format numérique devraient au moins consulter la page wikipedia IEEE754 et s'arrêter un moment pour réfléchir.
Adrian Ratnapala
Ce n’est pas malheureux. Voir la réponse de @CervEd. Il n'est pas lié à IEE754 ce qui est une bonne chose (même si la plupart des langages de programmation utilisent IEEE754 et nécessite donc un traitement supplémentaire en cas de NaN, etc.).
Ludovic Kuty
16

Pourriez-vous adapter le modèle d'objet nul et représenter dans votre JSON des valeurs telles que

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

Ensuite, lors de la vérification, vous pouvez vérifier le type

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

Je sais qu'en Java, vous pouvez remplacer les méthodes de sérialisation afin de mettre en œuvre une telle chose. Je ne sais pas d'où vient votre sérialisation, donc je ne peux pas donner de détails sur la façon de l'implémenter dans les méthodes de sérialisation.

Zoidberg
la source
1
hmmm ... c'est une réponse à une solution de contournement; Je ne demandais pas vraiment une solution de contournement, mais plutôt pourquoi ces valeurs excluaient. Mais +1 de toute façon.
Jason S
2
@Zoidberg: undefinedn'est pas un mot-clé, c'est une propriété sur l'objet global
olliej
2
@Zoidberg: undefined est une propriété sur l'objet global - ce n'est pas un mot-clé, donc "undefined" in thisrenvoie true dans la portée globale. Cela signifie également que vous pouvez faire undefined = 42et if (myVar == undefined)devenir (essentiellement) myVar == 42. Cela nous ramène aux débuts du javascript ecmascript nee où il undefinedn'existait pas par défaut, donc les gens l'ont fait var undefineddans le monde entier. Par conséquent, undefinedon ne pouvait pas faire un mot-clé sans casser des sites existants, et nous étions donc condamnés à tout jamais à avoir indéfini une propriété normale.
olliej
2
@olliej: Je n'ai aucune idée de la raison pour laquelle vous pensez qu'undefined est une propriété de l'objet global. Par défaut, la recherche de undefined est la valeur intégrée de undefined. Si vous le remplacez par "undefined = 42", lorsque vous accédez à undefined en tant que recherche de variable, vous obtenez la valeur remplacée. Mais essayez de faire "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined new =' + (xa === undefined)". Vous ne pouvez jamais redéfinir les valeurs internes de null, undefined, NaN ou Infinity, même si vous pouvez remplacer leurs recherches de symboles.
Jason S
2
@Jason undefinedest une propriété globale car elle est spécifiée comme telle. Consultez 15.1.1.3 de ECMAScript-262 3e éd.
kangax
11

Les chaînes "Infinity", "-Infinity" et "NaN" contraignent toutes les valeurs attendues dans JS. Je dirais donc que la bonne façon de représenter ces valeurs dans JSON est sous forme de chaînes.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

C'est juste dommage que JSON.stringify ne le fasse pas par défaut. Mais il y a un moyen:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"
teh_senaus
la source
1
0/0, etc., ne sont pas des JSON valides. Vous devez travailler dans les limites de la norme, et les chaînes font bien le travail.
teh_senaus
Au contraire, je pense que c'est la seule solution pratique, mais je vais faire une fonction qui renvoie NaN si la valeur d'entrée est "NaN", etc. La façon dont vous effectuez la conversion est sujette à l'injection de code.
Marco Sulla
3
Les valeurs JSON ne peuvent pas être des expressions arithmétiques ... l'objectif de séparer le standard de la syntaxe littérale du langage est de rendre JSON déesérialisable sans l'exécuter en tant que code. Je ne sais pas pourquoi nous n'avons pas pu avoir NaNet Infinityajouté des valeurs de mot clé comme trueet false, cependant.
Mark Reed
Pour le rendre plus explicite, nous pouvons utiliser Number("Infinity"), Number("-Infinity")etNumber("NaN")
HKTonyLee
C'est un travail magique. JSON.parse("{ \"value\" : -1e99999 }")retournez facilement { value:-Infinity }en javascript. Seulement, il n'est tout simplement pas compatible avec le type de numéro personnalisé qui pourrait être plus grand que cela
Thaina
7

Si vous avez accès au code de sérialisation, vous pouvez représenter Infinity comme 1.0e + 1024. L'exposant est trop grand pour être représenté dans un double et lorsqu'il est désérialisé, il est représenté par Infinity. Fonctionne sur webkit, pas sûr des autres analyseurs json!

Kuwerty
la source
4
IEEE754 prend en charge les nombres à virgule flottante de 128 bits, donc 1.0e5000 est meilleur
Ton Plomp
2
Ton: 128 bits ont été ajoutés plus tard. Et s'ils décident d'ajouter 256 bits? Ensuite, vous devrez ajouter plus de zéros et le code existant se comportera différemment. Infinitysera toujours Infinity, alors pourquoi ne pas soutenir cela?
flying mouton
1
Idée brillante! J'étais sur le point de passer à un format différent ou d'ajouter un code de contournement encombrant à mon analyseur. Pas idéal pour tous les cas, mais dans mon cas, où l'infini sert simplement de cas de bord optimisé à une séquence convergente, c'est juste parfait et même si une plus grande précision serait introduite, elle serait toujours la plupart du temps correcte. Merci!
Ou Sharir
3
1, -1 et 0 ..... les nombres parfaitement valides / analysables deviennent ces trois valeurs spéciales lorsque vous les ajoutez simplement /0à la fin. Il est facilement analysable, immédiatement visible et même évaluable. Il est inexcusable qu'ils ne l'aient pas encore ajouté à la norme: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << Pourquoi pas? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Pas d'excuses.
Triynko
1

La norme IEEE Std 754-2008 actuelle inclut des définitions pour deux représentations à virgule flottante 64 bits différentes: un type à virgule flottante décimal 64 bits et un type à virgule flottante 64 bits binaire.

Après avoir arrondi, la chaîne .99999990000000006est la même que .9999999dans la représentation binaire 64 bits IEEE, mais ce n'est PAS la même que .9999999dans la représentation décimale 64 bits IEEE. Dans les arrondis à virgule flottante décimale IEEE 64 bits .99999990000000006à la valeur .9999999000000001qui n'est pas la même que la .9999999valeur décimale .

Étant donné que JSON ne traite que les valeurs numériques comme des chaînes numériques de chiffres décimaux, il n'y a aucun moyen pour un système qui prend en charge les représentations à virgule flottante binaire et décimale IEEE (comme IBM Power) de déterminer laquelle des deux valeurs numériques à virgule flottante IEEE possibles est prévu.

Steven Hobbs
la source
Qu'est-ce que cela a à voir avec la question? (qui concerne Infinity et NaN)
Bryan
1

Solution de contournement potentielle pour des cas tels que {"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;
   });

L'idée générale est de remplacer les occurrences de valeurs invalides par une chaîne que nous reconnaîtrons lors de l'analyse et de la remplacer par la représentation JavaScript appropriée.

SHamel
la source
Je ne sais pas pourquoi cette solution a reçu un vote défavorable, car franchement, si vous êtes confronté à une situation où votre chaîne JSON contient des valeurs Infinity ou IsNaN, elle échouera lorsque vous essayez de l'analyser. En utilisant cette technique, vous remplacez d'abord les occurrences de IsNaN ou Infinity par autre chose (pour les isoler de toute chaîne valide pouvant contenir ces termes), et utilisez le JSON.parse (chaîne, rappel) pour renvoyer les valeurs JavaScript valides appropriées. J'utilise cela dans le code de production et je n'ai jamais eu de problème.
SHamel
Cela ne gâcherait-il pas Infinity à l'intérieur des chaînes? Pour de nombreux cas d'utilisation, il est probablement prudent de supposer que ce n'est pas un problème, mais la solution n'est pas totalement robuste.
olejorgenb
1

La raison est indiquée à la page ii de la norme ECMA-404 La syntaxe d'échange de données JSON, 1ère édition

JSON est indépendant des nombres. Dans n'importe quel langage de programmation, il peut y avoir une variété de types de nombres de différentes capacités et compléments, fixes ou flottants, binaires ou décimaux. Cela peut rendre difficile l'échange entre différents langages de programmation. JSON offre à la place uniquement la représentation des nombres que les humains utilisent: une séquence de chiffres. Tous les langages de programmation savent comment donner un sens aux séquences de chiffres même s'ils ne sont pas d'accord sur les représentations internes. Cela suffit pour permettre l'échange.

La raison n'est pas, comme beaucoup l'ont prétendu, due aux représentations NaNet au Infinityscript ECMA. La simplicité est un principe de conception de base de JSON.

Parce que c'est si simple, on ne s'attend pas à ce que la grammaire JSON change un jour. Cela donne à JSON, en tant que notation fondamentale, une stabilité énorme

CervEd
la source
-3

Si comme moi vous n'avez aucun contrôle sur le code de sérialisation, vous pouvez traiter les valeurs NaN en les remplaçant par null ou toute autre valeur comme un petit hack comme suit:

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

En substance, .fail sera appelé lorsque l'analyseur json d'origine détecte un jeton invalide. Ensuite, une chaîne de remplacement est utilisée pour remplacer les jetons non valides. Dans mon cas, c'est une exception pour le sérialiseur de renvoyer des valeurs NaN, donc cette méthode est la meilleure approche. Si les résultats contiennent normalement un jeton non valide, vous feriez mieux de ne pas utiliser $ .get mais plutôt de récupérer manuellement le résultat JSON et d'exécuter toujours le remplacement de chaîne.

user1478002
la source
21
Intelligent, mais pas totalement infaillible. Essayez-le avec{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ
1
et vous devez utiliser jQuery. Je n'ai pas $ .get ().
Jason S