为什么JSON.parse会损坏大数字,如何解决这个问题?

网络安全侠
• 阅读 2100
微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

从10多年前JSON在线编辑器的早期开始,用户经常反映编辑器有时会破坏他们JSON文档中的大数字的问题。直到现在,我们也没能解决这个问题。在这篇文章中,我们深入解释了这个问题,并展示如何在JSON Editor Online中解决这个问题。

大数字的问题

大多数 Web 应用程序处理来自服务器的数据。这些数据以纯文本的JSON文档形式被接收,并被解析成一个JavaScript对象或数组,这样我们就可以读取属性并做一些事情。通常情况下,数据的解析是使用JSON.parse函数进行的,该函数内置于JavaScript中,非常快速和方便。

JSON数据格式极其简单,而且它是JavaScript的一个子集。所以它与JavaScript完全可以互换。你可以将一个JSON文档粘贴到一个JavaScript文件中,这就是有效的JavaScript。

在JavaScript中使用JSON应该不会出现任何问题,但有一种棘手的情况可能会破坏数据:大数字。这是一个有效的JSON字符串:

{"count": 9123372036854000123}

当我们将其解析为JavaScript并读取 "count" 键时,我们会得到:

9123372036854000000

解析后的数值被破坏了:最后三位数字被重置为零。这是否是一个问题,取决于这些最后的数字是否确实有意义,但一般来说,知道这种情况可能会发生,可能会给你一种不舒服的感觉。

为什么大数字会被JSON.parse破坏?

9123372036854000123 这样的长数字既是有效的 JSON 也是有效的 JavaScript。当JavaScript 将数值解析为数字时,事情就出错了。最初,JavaScript 只有一种数字类型。Number。这是一个64位的浮点值,类似于C++、Java或C#中的Double值。这种浮点值可以存储大约16位数字。因此,它不能完全代表像9123372036854000123这样的数字,它有19位数字。在这种情况下,最后三位数字会丢失,破坏了该值。

在用浮点数存储分数时也会发生同样的情况:当你在 JavaScript 中计算 1/3时,结果是:

0.3333333333333333

在现实中,该值应该有无限的小数,但 JavaScript 的数字在大约 16位 之后就停止了。

那么,JSON文档中像9123372036854000123这样的大数字是怎么来的呢?嗯,其他语言如Java或C#确实有其他数字数据类型,如LongLong是一个64位的值,可以容纳最多20位的整数。它能容纳更多数字的原因是,它不需要像浮点值那样存储指数值。因此,在像Java这样的语言中,你可以有一个Long值,它不能在JavaScript的Number类型中正确表示,或者在其他语言中的Double类型中正确表示。

JavaScript 的 Number(或者更好:任何浮点数值)还有一些限制:数值可以溢出或下溢。例如,1e+500会变成Infinity,而1e-500会变成0。不过,这些限制在实际应用程序中很少成为问题。

如何防止数字被 JSON.parse 破坏?

多年来,这个用 JavaScript 解析大数字的问题一直是https://jsoneditoronline.org/ 的用户反复要求的。像大多数基于网络的JSON编辑器一样,它也使用了本地的JSON.parse函数和常规的JavaScript数字,所以它受到了上述的限制。

第一个想法可能是:等等,但是 JSON.parse 有一个可选的reviver参数,允许你用不同的方式来解析内容。但问题是,首先文本被解析成一个数字,接下来,它被传递给reviver。所以到那时,已经太晚了,值已经被破坏了。

为了解决这个问题,根本不能使用内置的JSON.parse,必须使用一个不同的JSON解析器。对此有各种优秀的解决方案:lossless-jsonjson-bigint、js-jon-bigint或json-source-map

这些库中的大多数都采取了务实的方法,将长数字直接解析为JavaScript相对较新的BigInt数据类型。lossless-json库是专门为JSON Editor Online开发的。它采取了比JSON BigInt解决方案更加灵活和强大的方法。

默认情况下,lossless-json 将数字解析成一个轻量级的LosslessNumber类,该类将数字值作为一个字符串持有。这保留了任何数值,甚至还保留了格式化,比如数值4.0中的尾部零。当对其进行操作时,LosslessNumber将被转换为NumberBigInt,或者在不安全时抛出一个错误。

该库允许你传递你自己的数字解析器,所以你可以应用你自己的策略来处理数字值。也许你想把长的数字值转换成BigInt,或者把数值传给某个BigNumber库。你可以选择是否要在数字信息丢失时抛出一个异常,或者默默地忽略某些类别的信息丢失。

因此,比较本地JSON.parse函数和lossless-json,会得到以下结果:

import { parse, stringify } from 'lossless-json'
const text = '{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}'
// JSON.parse will lose some digits and a whole number:
console.log(JSON.stringify(JSON.parse(text)))
// '{"decimal":2.37,"long":9123372036854000000,"big":null}'
// WHOOPS!!!
// LosslessJSON.parse will preserve all numbers and even the formatting:
console.log(stringify(parse(text)))
// '{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}'

使用LosslessJSON解析器是否能解决所有问题?

答案是并不能。这取决于你在解析数据后想做什么,但通常情况下,你想用它做一些事情。在屏幕上显示数据,验证它,比较它,排序它,等等。例如,在JSON Editor Online中,你可以编辑数值,转换文档(查询、过滤、排序等),比较两个文档,或者根据JSON模式验证一个文档。一旦你引入BigInt值或LosslessNumbers,你想执行的所有操作都需要支持这些类型的值。

拥有 BigInt 值或 LosslessNumbers 的数据很可能给不了解这些数据类型的第三方库带来问题。例如,JSON Editor Online支持将你的JSON数据导出到CSV,并使用优秀的json2csv库来实现。

这个库不知道BigIntLosslessNumber类型,不会正确串联这些数据类型。为了使其正常工作,包含LosslessNumbersBigInt值的JSON数据必须首先被转换为该库所能理解的数据。

即使没有第三方库的参与,与BigInt值一起工作也会导致棘手的问题。当对大整数和普通数字的混合操作时,JavaScript可以默默地将一种数字类型强制转化为另一种,这可能会导致错误。下面的代码例子显示了这是如何出错的。

const a = 91111111111111e3 // a regular number
const b = 91111111111111000n // a bigint
console.log(a == b) // returns false (should be true)
console.log(a > b) // returns true (should be false)

在这个例子中,你看到两个常数ab持有相同的数字值。但是一个是数字,另一个是BigInt,用这些东西和普通的操作符(如==>)一起使用会导致错误的结果。

结论:要让大数字在一个应用程序中工作,可能需要大量的努力。因此,最好的办法是尽量避免在一开始就处理这些问题。

如果你真的要处理大数值,你必须使用一个替代的JSON分析器,如lossless-json。为了防止陷入与拥有BigIntLosslessNumber数据类型有关的难以调试的问题,使用TypeScript明确定义你的数据模型是很有帮助的。这样,你就可以事先知道哪些地方需要能够处理这些特殊的数据类型,你就可以采取行动,而不是让你的应用程序默默地失败。

在线JSON编辑器现在可以安全地处理大数字了

从今天起,JSON Editor Online已经完全支持大数字,所以你不必再担心损坏的数值。它已经集成了lossless-json库,并确保编辑器的所有功能都能处理大数字:从格式化、排序和查询到导出到CSV。作为一个副作用,它现在甚至保持了数字的格式化,而且由于新的LosslessJSON解析器,现在可以检测到重复的键。

试一试:https://jsoneditoronline.org/#left=json.%7B%20%22using%22:%20...,%20%22formatted%20number%22:%204.0,%20%22long%22:%209123372036854000123,%20%22large%22:%201e500,%20%22small%22:1e-500%20%7D

现在,使用lossless-json有一个缺点:它比原生内置的JSON.parse慢得多。这只是大的JSON对象或数组的问题,对于大于10MB的文件,它可能会很明显。为了仍能顺利地处理大文件,JSON Editor Online允许你选择你想使用的解析器,默认情况下,它会自动为你选择最合适的解析器。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

来源:https://jsoneditoronline.org/indepth/parse/why-does-json-pars...

交流

有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

为什么JSON.parse会损坏大数字,如何解决这个问题?

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
4年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这