JSON忽略了Infinity和NaN;ECMAScript中的JSON状态?


179

知道为什么JSON遗漏了NaN和+/- Infinity吗?如果它们包含NaN或+/-无穷大值,则Javascript处于一种奇怪的情况下,否则本来可以序列化的对象就无法实现。

看起来像是一成不变的:请参阅RFC4627ECMA-262(第24.5.2节,JSON.stringify,注意4,最后编辑的ECMA-262 pdf第683页):

有限数字被字符串化,好像通过调用ToString(number)NaN和Infinity(不考虑符号)均表示为String null


我在任何一个文档中都找不到该报价。
wingedsubmariner

1
修复它,看起来好像有过时的参考/过时的编辑。
詹森·S

Answers:


90

Infinity并且NaN不是关键字或任何特殊内容,它们只是全局对象的属性(按原样undefined),因此可以更改。因此,JSON不在规范中包含它们-本质上,如果您使用eval(jsonString)或,则任何真正的JSON字符串在EcmaScript中都应具有相同的结果JSON.parse(jsonString)

如果允许,那么有人可以注入类似于

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

进入论坛(或其他),然后该网站上的任何json使用情况都可能受到影响。


29
如果评估为1/0,则得到无穷大;如果评估为-1/0,则得到-Infinity;如果评估为0/0,则得到NaN。
杰森S

9
但是术语NaNInfinity是属性名称,因此String(1/0)生成的字符串"Infinity"只是值无穷大的字符串表示形式。ES 既不能代表它,也不能代表它NaNInfinity因为您必须使用表达式(例如1 / 0、0 / 0等)或属性查找(引用InfinityNaN)。由于那些需要执行代码,因此它们不能包含在JSON中。
olliej

16
就安全性而言,一个不错的JSON解析器在转换NaN时所要做的就是产生值0/0(而不是评估符号NaN),无论返回什么,该值都会返回“真实的” NaN将符号NaN重新定义为。
杰森S

33
@olliej:您认为NaN不是文字,我对Java语言的了解不足以判断JavaScript语义。但是对于存储双精度浮点数的文件格式,应该有一种定义IEEE浮点数的方法,即使用文字NaN / Infinity / NegInfinity。这些是64位双精度的状态,因此应该可以表示。有些人依靠他们(出于某种原因)。他们可能被遗忘了,因为JSON / Javascript起源于Web开发而不是科学计算。
wirrbel 2013年

35
JSON完全忽略NaN,Infinity和-Infinity的完全有效和标准浮点数状态是100%绝对错误。从本质上讲,JSON决定支持IEEE浮点值的任意子集,由于它们很难或某些东西而无知地忽略了三个特定值。不。可评估性甚至不是借口,因为这样的数字本可以被编码为文字1/0,-1 / 0和0/0。它们将是附加有“ / 0”的有效数字,不仅易于检测,而且实际上可以同时评估为ES。别找借口。
Triynko

55

关于原始问题:我同意用户“ cbare”,因为这是JSON中的不幸遗漏。IEEE754将它们定义为浮点数的三个特殊值。因此,JSON无法完全代表IEEE754浮点数。实际上,情况甚至更糟,因为ECMA262 5.1中定义的JSON甚至没有定义其编号是否基于IEEE754。由于为ECMA262中的stringify()函数描述的设计流程确实提到了三个特殊的IEEE值,因此人们可以怀疑实际上是打算支持IEEE754浮点数。

作为另一个数据点,与以下问题无关:XML数据类型xs:float和xs:double声明它们基于IEEE754浮点数,并且确实支持这三个特殊值的表示(请参阅W3C XSD 1.0第2部分) ,数据类型)。


5
我同意这很不幸。但是,JSON数字未指定确切的浮点格式也许是一件好事。甚至IEEE754也指定了许多格式-不同的大小以及十进制和二进制指数之间的区别。JSON特别适合十进制,因此,如果某些标准将其固定为二进制,那将是一个遗憾。
Adrian Ratnapala 2014年

5
@AdrianRatnapala +1的确:JSON数字具有潜在的无限精度,因此比IEEE规范要好得多,因为它们没有大小限制,没有精度限制并且没有舍入效果(如果串行器可以处理)。
Arnaud Bouchez,2015年

2
@ArnaudBouchez。也就是说,JSON应该仍然支持代表NaN和+ -Infinity的字符串。即使不应该将JSON固定为任何IEEE格式,定义数字格式的人们也应该至少查看Wikipedia页面IEEE754并停下来思考一下。
Adrian Ratnapala 2015年


这不是不幸的。请参阅@CervEd的答案。它与IEE754无关,这是一件好事(即使大多数编程语言都使用IEEE754,因此在NaN等情况下也需要额外的处理)。
Ludovic Kuty

16

您能否适应空对象模式,并在JSON中表示以下值:

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

然后在检查时,您可以检查类型

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

我知道在Java中,您可以重写序列化方法以实现这样的事情。不知道从何处进行序列化,因此我无法提供有关如何在序列化方法中实现的详细信息。


1
嗯...这是解决方法的答案;我并不是真正要求解决方法,而是为什么要排除这些值。但是无论如何+1。
杰森S

2
@Zoidberg:undefined不是关键字,它是全局对象的属性
olliej

2
@Zoidberg:undefined是全局对象的属性-它不是关键字,因此"undefined" in this在全局范围内返回true。这也意味着您可以做到undefined = 42if (myVar == undefined)(基本上)成为myVar == 42。这可以追溯到ecmascript nee javascript的早期undefined,默认情况下不存在,因此人们只是var undefined在全球范围内这样做。因此undefined,在不破坏现有站点的情况下就不能成为关键字,因此我们注定要一直将undefined定义为常规属性。
olliej

2
@olliej:我不知道为什么您认为undefined是全局对象的属性。默认情况下,对undefined的查找是undefined的内置值。如果使用“ undefined = 42”覆盖它,那么当您访问undefined作为变量查找时,您将获得覆盖的值。但请尝试执行“ zz = undefined; undefined = 42; x = {};'undefined old ='+(xa === zz)+',undefined new ='+(xa === undefined)”。即使您可以覆盖它们的符号查找,也永远无法重新定义其内部值null,undefined,NaN或Infinity。
杰森S

2
@Jason undefined是全局属性,因为它是这样指定的。请参阅ECMAScript-262第3版的15.1.1.3。
kangax

11

字符串“ Infinity”,“-Infinity”和“ NaN”都强制转换为JS中的期望值。因此,我认为在JSON中表示这些值的正确方法是字符串。

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

这只是一个耻辱JSON.stringify默认不会执行此操作。但是有一种方法:

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

1
0/0等无效的JSON。您必须在标准范围内工作,而字符串可以很好地完成工作。
teh_senaus

相反,我认为这是唯一可行的解​​决方案,但是如果输入值是“ NaN”等,我将执行一个返回NaN的函数。执行转换的方式易于注入代码。
Marco Sulla

3
JSON值不能是算术表达式...使标准与语言文字语法分开的目的是使JSON可反序列化,而不将其中任何代码作为代码执行。不知道为什么我们不能将NaNInfinity添加为关键字值,例如truefalse
马克·里德

为了使它更加明确,我们可以使用Number("Infinity")Number("-Infinity")并且Number("NaN")
HKTonyLee

这就像魔术一样。JSON.parse("{ \"value\" : -1e99999 }")轻松返回{ value:-Infinity }javascript。只有它与可能更大的自定义数字类型不兼容
Thaina

7

如果您有权访问序列化代码,则可以将Infinity表示为1.0e + 1024。指数太大,无法以双精度表示,反序列化时表示为无穷大。可在webkit上使用,不确定其他json解析器!


4
IEEE754支持128位浮点数,因此1.0e5000更好
Ton Plomp

2
吨:后来添加了128位。如果他们决定增加256位怎么办?然后,您将不得不添加更多的零,并且现有代码的行为将有所不同。Infinity会一直Infinity如此,那为什么不支持呢?
飞羊

1
聪明的主意!我正要切换到其他格式,或者将繁琐的解决方法代码添加到解析器中。在每种情况下都不是理想的选择,但是在我的情况下,无穷大只是收敛序列的优化边缘情况,它是完美的,即使引入更大的精度,在大多数情况下也是正确的。谢谢!
或Sharir

3
1,-1和0 .....完全有效/可解析的数字,当您简单地将其添加/0到它们的末尾时,就成为这三个特殊值。它易于解析,立即可见甚至可评估。他们尚未将其添加到标准中是不可原谅的:{"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} <<为什么不呢? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'。别找借口。
Triynko 2015年


1

当前的IEEE Std 754-2008包括两种不同的64位浮点表示形式的定义:十进制64位浮点类型和二进制64位浮点类型。

舍入后的字符串.99999990000000006相同.9999999的IEEE二进制64位表示,但它是一样的.9999999在IEEE十进制的64位表示。在64位IEEE浮点小数.99999990000000006四舍五入为值.9999999000000001这是不一样的小数.9999999值不同的值。

由于JSON仅将数字值视为十进制数字字符串,因此无法同时支持IEEE二进制和十进制浮点表示形式的系统(例如IBM Power)来确定两个可能的IEEE数字浮点值中的哪一个是预期的。


这与问题有什么关系?(与Infinity和NaN有关)
Bryan

1

对于诸如{“ 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;
   });

一般的想法是用解析时将识别的字符串替换出现的无效值,并用适当的JavaScript表示形式将其替换回来。


我不知道为什么此解决方案会失败,因为坦率地说,如果您遇到JSON字符串包含Infinity或IsNaN值的情况,则在尝试解析它时将失败。使用此技术,您首先用其他东西替换IsNaN或Infinity的出现(以将它们与可能包含这些术语的任何有效字符串隔离开),然后使用JSON.parse(string,callback)返回正确的有效JavaScript值。我在生产代码中使用了它,从来没有任何问题。
沙梅尔(Shamel),

这不会把Infinity弄乱吗?对于许多用例,可以假定它不是问题,这很安全,但是该解决方案并不完全可靠。
olejorgenb

1

原因在标准ECMA-404第1版JSON数据交换语法的第ii页中进行了说明

JSON与数字无关。在任何编程语言中,可以有各种容量和补码形式的数字类型,固定或浮动,二进制或十进制。这会使不同编程语言之间的交换变得困难。JSON而是仅提供人类使用的数字表示形式:数字序列。所有编程语言都知道如何理解数字序列,即使它们在内部表示形式上存在分歧。这足以允许互换。

正如许多人所声称的,原因并不是由于NaNInfinityECMA脚本的表示。简单性是JSON的核心设计原则。

因为它是如此简单,所以JSON语法不会发生变化。作为基本符号,这为JSON提供了极大的稳定性


-3

如果像我一样您无法控制序列化代码,则可以通过将NaN值替换为null或任何其他值来处理NaN值,如下所示:

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

本质上,当原始json解析器检测到无效令牌时,将调用.fail。然后使用字符串替换来替换无效令牌。在我的情况下,序列化程序返回NaN值是一个例外,因此此方法是最好的方法。如果结果通常包含无效令牌,则最好不要使用$ .get,而应手动检索JSON结果并始终运行字符串替换。


21
聪明,但并非完全万无一失。试试看{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ 2013年

1
并且您必须使用jQuery。我没有$ .get()。
杰森S
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.