如何在Node.js中解析包含“NaN”的JSON字符串

35
有一个 node.js 应用程序,接收包含文字 NaN 的 JSON 数据字符串,例如:
 "[1, 2, 3, NaN, 5, 6]"

这会导致Node.js中的JSON.parse(...)崩溃。如果可能的话,我想将其解析为一个对象。
我知道NaN不是JSON规范的一部分。大多数SO链接(sending NaN in json)建议修复输出。
在这里,尽管数据是由我无法控制的商业Java库生成的,但我可以看到源代码。它是由Google的Gson库生成的:
private Gson gson = (new GsonBuilder().serializeSpecialFloatingPointValues().create()); 
... 
gson.toJson(data[i], Vector.class, jsonOut)

这似乎是一个合法的来源。根据Gson API Javadoc所说,我应该能够解析它:
引用: JSON规范的第2.4节禁止特殊双精度值(NaN、Infinity、-Infinity)。然而,Javascript规范(见第4.3.20、4.3.22、4.3.23节)允许这些值作为有效的Javascript值。此外,大多数JavaScript引擎将在JSON中接受这些特殊值而没有问题。因此,在实际层面上,即使JSON规范不允许它们,接受这些值作为有效的JSON也是有意义的。
尽管如此,在Node.js和Chrome中都失败了:JSON.parse('[1,2,3,NaN,"5"]') 是否有在JSON.parse()中设置的标志?或者有一种替代解析器可以接受NaN作为字面量?
我已经谷歌了一段时间,但似乎找不到关于这个问题的文档。

PHP:如何将无穷大或NaN数字编码为JSON?


7
我觉得很讽刺,Google的Gson项目说JSON解析器应该允许这些字面量,而不容忍它们的JavaScript引擎却是Google自己的V8。 - cdhowie
@cdhowie的Node JSON解析器只是遵循规范。 "讽刺"只是违反了健壮性原则 - Matt Ball
4
不,具有讽刺意味的是,谷歌的一个项目说:“大多数JavaScript引擎都会接受这些特殊值在JSON中而无需问题”,而谷歌的JavaScript引擎却是其中之一的例外,因为Chrome也不喜欢这个字符串。 - cdhowie
1
第1.2节描述了JSON。然而,TCP/IP协议支持许多其他格式,只要你付出足够的努力或不考虑安全性,就可以解码。因此,我们想,为什么要限制自己只使用JSON呢? - shannon
5个回答

59
您有一个接收包含文字NaN的JSON数据字符串的node.js应用程序,那么您的NodeJS应用程序并没有接收到JSON,而是接收到了模糊的类JSON文本。NaN不是有效的JSON令牌,有三种选择:
1.将源代码正确生成JSON。
这显然是首选方法。数据不是JSON,应该修复,这将解决问题。
2.简单地容忍NaN:
您可以在解析之前将其替换为null,例如:
var result = JSON.parse(yourString.replace(/\bNaN\b/g, "null"));

...然后处理结果中的null。但这样做太过简单,它并不考虑字符串中可能会出现字符NaN的情况。
另一种方法是借鉴Matt Ball的reviver思路(现已删除),您可以将其更改为特殊字符串(例如"***NaN***"),然后使用reviver将其替换为真正的NaN
var result = JSON.parse(yourString.replace(/\bNaN\b/g, '"***NaN***"'), function(key, value) {
    return value === "***NaN***" ? NaN : value;
});

...但这样做有一个问题,它有点简单化,假设字符NaN从未出现在适当的位置。

3. 使用(颤抖!)eval

如果您知道并且信任此数据的来源,并且不存在在传输过程中被篡改的可能性,则可以使用eval而不是JSON.parse来解析它。由于eval允许完整的JavaScript语法,包括NaN,所以可以使用。希望我已经足够强调了,我只会在非常非常非常少的情况下推荐这样做。但是请记住,eval允许执行任意代码,因此如果存在字符串被篡改的可能性,请勿使用。


4
假设字符串中从不出现文本“NaN”。要正确处理这一点,需要解析JSON以确定无效令牌的位置——在那一点上,最好将解析树转换为对象。因此,基本上,正确的解决方案是编写自己的NaN容忍的JSON解析器。 - cdhowie
1
哇!我本来以为会因为找不到一个有良好文档记录的选项而被羞辱。所以从好的方面来看,看起来那个选项根本不存在。 - prototype
@user645715:哈哈,确实不是。:-) 很高兴能帮到你! - T.J. Crowder
2
我选择了第三个选项,值得一提的是你需要在输入中添加括号:function myParseJSON(almost_json) { return eval("(" + almost_json + ")") } - r3m0t
@lordscales91:问题在于数据源生成了无效的JSON。因此,#1建议在源头修复它。#2基本上建议您在评论中建议的那样,通过预处理字符串,然后后处理解析结果。 - T.J. Crowder
显示剩余2条评论

7
当您处理数学或行业数据时,NaN(通常也是无穷大)非常方便,并且自IEEE754以来已成为行业标准。
这就是为什么一些库,特别是GSON,让您在它们生成的JSON中包含它们,失去了标准的纯度但增加了理智。
复兴和正则表达式的解决方案在交换复杂动态对象时并不可靠。
eval 也存在问题,其中之一是当 JSON 字符串很大时容易在 IE 上崩溃,另一个是安全风险。
这就是为什么我写了一个特定的解析器(在生产中使用):JSON.parseMore

这太棒了。在Node.js中,为大字符串添加括号会使用大量内存,而这种方法避免了这种情况。 - prototype
特别是GSON,让您将它们包含在它们生成的JSON中,失去标准纯度而获得理智。哈利路亚。非常感谢。看起来jackson也有一个QUOTE_NON_NUMERIC_NUMBERS参数。https://fasterxml.github.io/jackson-core/javadoc/2.4/com/fasterxml/jackson/core/JsonGenerator.Feature.html - Pavel Komarov
重现步骤:
  1. 获取gson jar包并将其放在终端所在位置。
  2. `import com.google.gson.Gson;
public class test_gson { public static void main(String ... args) { String to_parse = "{"val":NaN}"; Foo foo = new Gson().fromJson(to_parse, Foo.class); System.out.println("foo val=" + foo.val + ", is nan=" + foo.val.isNaN()); } }class Foo { public Float val; } 3.javac -cp gson-2.10.1.jar:. test_gson.java 4.java -cp gson-2.10.1.jar:. test_gson->foo val=NaN, is nan=true`
- Pavel Komarov

7
你可以使用 JSON5 库。该项目页面上的一句话引述如下:
JSON5 数据交换格式(JSON5)是 JSON 的超集,旨在通过扩展其语法以包括 ECMAScript 5.1 中的某些产生物来减轻 JSON 的一些限制。
这个 JavaScript 库是 JSON5 解析和序列化库的官方参考实现。
正如你所期望的那样,它支持解析 NaN(与 Python 等的序列化兼容)。
JSON5.parse("[1, 2, 3, NaN, 5, 6]")
> (6) [1, 2, 3, NaN, 5, 6]

0

对于TJ Crowder已经详尽的回答,我只是想做一个小小的补充,我更倾向于使用

var result = JSON.parse(yourString.replace(/\bNaN\b/g, '"NaN"'));

因为我实际上需要知道它是否为NaN值。

此外,如果默认的JSON解析失败并且数据以字符串形式出现,我会在fetch或axios GET请求内执行此操作。

const StringConstructor = "".constructor;

if (data.constructor === StringConstructor) {
    data = JSON.parse(tableData.data.replace(/\bNaN\b/g, '"NaN"'))
}

-1

正确的解决方案是重新编译解析器,并向源代码库贡献一个“allowNan”布尔标志。这是其他库所采用的解决方案(例如Python的库)。

良好的JSON库将使用正确的标志宽松地解析几乎任何类似于JSON的内容(Perl的JSON.pm非常灵活)...但在编写消息时,它们会生成标准的JSON。

即:离开房间时比进入时更整洁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接