当你解析 JSON 时, Number 类型双精度丢失可以尝试使用 json-bn

2022-11-05 21:59:43 +08:00
 Jobing

支持 ESM 和 CJS 环境,开源地址: https://github.com/jobinben/json-bn

2680 次点击
所在节点    程序员
16 条回复
bertonzh
2022-11-05 23:05:29 +08:00
建议先看看别人是怎么做的:
https://github.com/sidorares/json-bigint

你这几十行靠粗糙的正则替换实现的 json bigint 支持,自己在小项目里面对付简单场景可以,就不要发到 github 和 npm 了哈。

parse('{"x": ": 12345678901234561111"}')

Uncaught SyntaxError: Expected ',' or '}' after property value in JSON at position 11
at JSON.parse (<anonymous>)
at e (<anonymous>:1:242)
at <anonymous>:1:1
bertonzh
2022-11-05 23:21:36 +08:00
其实你这个实现完善一下应该可以处理这个问题(再往前判断一下属性名的右引号)。

但是别忘了 JSON 里面有数组:

parse('[12345678901234561111]')

通过正则不可能完全识别数组中 Int 。
kid740246048
2022-11-06 02:01:02 +08:00
真要搞个正经的 json 解析库的话,还是得测试先行,github 上有很多相关的 test suite ,比如:
https://github.com/nst/JSONTestSuite
https://github.com/dscape/clarinet/tree/master/test
himself65
2022-11-06 03:27:31 +08:00
题主有点不要脸了吧……把别的项目 download 数还专门贴到自己项目 readme 里面

https://github.com/jobinben/json-bn/commit/e14e7951835bf97242f6e7f8dd7fcae166e83f43
wxf666
2022-11-06 04:04:48 +08:00
@bertonzh `js` 新手。感觉通过正则找到数字,并替换为 `bigint`,最后再 `eval` 或 `new Function`,也能读取含有大数字的 `json` 诶:

```javascript
s = '{"x": ": 12345678901234561111", "y": [-12345678901234561111, "12345678901234561111", -123456, "\\"12345678901234561111\\""]}';

eval('(' + s.replace(/"(?:\\?.)*?"|-?\d+/g, m => !isFinite(m) || Number.isSafeInteger(+m) ? m : m + 'n') + ')');
```

结果:

```javascript
{
  x: ": 12345678901234561111",
  y: [
  -12345678901234561111n,
  "12345678901234561111",
  -123456,
  "\"12345678901234561111\""
 ]
}
```
Red998
2022-11-06 10:24:41 +08:00
字符天下第一
yulon
2022-11-06 11:22:34 +08:00
用正则永远不要在函数名里带上 parse 😅
bertonzh
2022-11-06 16:25:46 +08:00
@wxf666 嗯,看上去这个思路是可以的,优先匹配掉字符串,再匹配的数字串就肯定是字符串外面的。
对转义的处理目前没有发现什么毛病。

@Jobing 楼主可以借鉴一下。
wxf666
2022-11-06 16:52:23 +08:00
@bertonzh 回头再看看,其实应该还没处理好有浮点数的 `json`。但思路是一样的,碰到字符串、浮点数,跳过就好。


如果担心 `eval` 了危险的 `js`,我觉得可以正则匹配下,看是不是全为 `json` 的元素:

`/^(?:字符串|整数|浮点|true|false|null|[[\]{},]|\s+)$/`


其实,我感觉可以:

1. 正则匹配出,并用一个数组记录下所有整数,再替换为在数组中的下标。
2. `JSON.parse()` 后,再遍历所有为整数的值,替换回来(这时候就可以决定用不用 `bigint` 了)

好处:

1. 这应该会比手动解析 `json` 快
2. 而且代码体积小(可能十来行就可以了)
3. 不用担心 `eval`、`new Function` 能不能用,危不危险的问题
wxf666
2022-11-06 16:53:55 +08:00
@bertonzh 写漏了:

`/^(?:字符串|整数|浮点|true|false|null|[[\]{},]|\s+)*$/`
bertonzh
2022-11-06 21:55:18 +08:00
@wxf666 你这个办法可不行,JS 遍历对象 key 的顺序并不是按照字面量 key 顺序来的,所以你无法通过数组按顺序记录整数。
另外,性能也不一定快,因为你做了三步:分词 + JSON.parse + 遍历替换,而手动解析只需要一趟(分词和生成结果同时处理)。(具体我没测过)


所以要么是用你最开始的 eval 方式。不过 eval 比 JSON.parse 更慢,因为 eval 处理的是 JS 代码,语法比 JSON 复杂很多倍。
或者用楼主的实现,即先替换成一个带特殊唯一标志的字符串,然后在 JSON.parse 的第二个回调参数里面做替换,或者在 parse 之后遍历。
wxf666
2022-11-07 01:21:17 +08:00
@bertonzh 试着写出来了:

*( V 站排版原因,行首有全角空格)*

```javascript
function parseJson(json) {

  function restore(obj) {
   if (typeof obj === 'number')
    return nums[obj];
   else if (Array.isArray(obj))
    obj.forEach((v, i) => obj[i] = restore(v));
   else if (typeof obj === 'object' && obj !== null)
    Object.keys(obj).forEach(k => obj[k] = restore(obj[k]));
   return obj;
 }

  let nums = [];
  return restore(JSON.parse(json.replace(/"(?:\\?.)*?"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g, m =>
   m[0] === '"' ? m : nums.push(/[.eE]/.test(m) || Number.isSafeInteger(+m) ? +m : BigInt(m)) - 1
 )));
}
```

拿了个本地 64.8 MB 的 `~/.conda/pkgs/cache/e5e4a514.json` 测了下速度: *( i5-8250U ,`nodejs` v16 )*

- #5 楼的方法:3.3 s
- 此楼方法:2.2 s = 正则替换 1.5 s + json 解析 0.4 s + 递归替换 0.3 s
- `json-bigint` *(`{useNativeBigInt: true}`)*:1.7 s
chenjiangui998
2022-11-07 09:38:24 +08:00
@himself65 太无耻了
bertonzh
2022-11-07 11:08:56 +08:00
@wxf666 原来是把数组序号替换进去了啊,可以可以
bertonzh
2022-11-07 11:13:49 +08:00
@wxf666 试试这样呢?

```
function parseJson(json) {
  let nums = [];
  return JSON.parse(json.replace(/"(?:\\?.)*?"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g, m =>
   m[0] === '"' ? m : nums.push(/[.eE]/.test(m) || Number.isSafeInteger(+m) ? +m : BigInt(m)) - 1
 ), (key, val) => typeof val === 'number' ? nums[val] : val);
}
```
wxf666
2022-11-07 11:44:21 +08:00
@bertonzh `JSON.parse` 还能这样用?学习了!

但耗时更久了,2.8 s 左右

会不会是有了转换器,就用不了内部高度优化的 `json` 解析器了。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/892983

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX