当前位置: 首页 > 科技观察

为什么JSON.parse会损坏大量数据,我该如何解决?

时间:2023-03-13 08:40:57 科技观察

自从10多年前在线JSON编辑器出现以来,用户经常报告说该编辑器有时会损坏他们的JSON文档中的大量数字。直到现在,我们还没有能够解决这个问题。在本文中,我们深入解释了这个问题,并展示了如何在JSONEditorOnline中解决它。大数字的问题大多数网络应用程序处理来自服务器的数据。此数据作为纯文本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只有一种数字类型。号码。这是一个64位浮点值,类似于C++、Java或C#中的Double。这样的浮点值可以存储大约16位数字。因此,它不能完全表示像9123372036854000123这样有19位数字的数字。在这种情况下,最后三位数字丢失,破坏了价值。用浮点数存储分数时也会发生同样的情况:当你在JavaScript中计算1/3时,结果是:0.3333333333333333实际上,该值应该有无限小数,但JavaScript的数字在大约16位之后就停止了。那么,像9123372036854000123这样的大数字在JSON文档中是从哪里来的呢?好吧,其他语言如Java或C#确实有其他数字数据类型,如Long。Long是一个64位值,最多可以容纳20位整数。它可以容纳更多数字的原因是它不需要像浮点值那样存储指数值。因此,在像Java这样的语言中,您可以拥有一个无法在JavaScript的Number类型或其他语言的Double类型中正确表示的Long值。JavaScript的数字(或更好:任何浮点值)有一些限制:数字可以溢出或下溢。例如,1e+500会变成Infinity,1e-500会变成0。不过,这些限制在实际应用中很少成为问题。如何防止数字被JSON.parse破坏?多年来,https://jsoneditoronline.org/的用户一直在反复询问这个在JavaScript中解析大数的问题。与大多数基于Web的JSON编辑器一样,它也使用本机JSON.parse函数和常规JavaScript数字,因此受到上述限制。第一个想法可能是:等等,但是JSON.parse有一个可选的reviver参数,允许您以不同的方式解析内容。但问题是,首先将文本解析为数字,然后将其传递给reviver。所以到那时,为时已晚,价值已被破坏。要解决此问题,根本无法使用内置的JSON.parse,必须使用不同的JSON解析器。有各种优秀的解决方案:lossless-json、json-bigint、js-jon-bigint或json-source-map。这些库中的大多数都采用务实的方法,将长数字直接解析为JavaScript相对较新的BigInt数据类型。lossless-json库是专门为JSONEditorOnline开发的。它采用比JSONBigInt解决方案更灵活、更强大的方法。默认情况下,lossless-json将数字解析为轻量级的LosslessNumber类,该类将数字值保存为字符串。这会保留任何数值,甚至格式,例如值4.0中的尾随零。LosslessNumber在对其进行操作时,会被转换为Number或BigInt,如果不安全则抛出错误。该库允许您传递自己的数字解析器,因此您可以应用自己的策略来处理数值。也许您想将长数值转换为BigInt,或者将值传递给某些BigNumber库。您可以选择是在缺少数字信息时抛出异常,还是静默忽略某些类别的缺失信息。因此,对比本地的JSON.parse函数和lossless-json,会得到如下结果:import{parse,stringify}from'lossless-json'consttext='{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}'//JSON.parse会丢失一些数字和一个整数:console.log(JSON.stringify(JSON.parse(text)))//'{"decimal":2.37,"long":9123372036854000000,"big":null}'//WHOOPS!!!//LosslessJSON.parse将保留所有数字甚至格式:console.log(stringify(parse(text)))//'{"decimal":2.370,"long":9123372036854000123,"big":2.3e+500}'使用LosslessJSON解析器能解决所有问题吗?答案是不。这取决于你想在解析数据后对数据做什么,但通常,你想用它做一些事情。在屏幕上显示数据、验证、比较、排序等。例如,在JSONEditorOnline中,您可以编辑值、转换文档(查询、过滤、排序等)、比较两个文档或验证文档针对JSON模式。一旦你引入了BigInt值或LosslessNumbers,你要执行的所有操作都需要支持这些类型的值。具有BigInt值或LosslessNumbers的数据很可能会给不理解这些数据类型的第三方库带来问题。例如,JSONEditorOnline支持将您的JSON数据导出为CSV,并使用出色的json2csv库来执行此操作。该库不知道BigInt或LosslessNumber类型,并且不会正确连接这些数据类型。为了使其正常工作,必须首先将包含LosslessNumbers或BigInt值的JSON数据转换为库可以理解的数据。即使没有第三方库的参与,使用BigInt值也会导致棘手的问题。当混合使用大整数和普通数字时,JavaScript可以默默地将一种数字类型强制转换为另一种数字类型,这可能会导致错误。下面的代码示例显示了这是如何出错的。consta=91111111111111e3//一个常规数字constb=9111111111111000n//abigintconsole.log(a==b)//返回false(应该是true)console.log(a>b)//返回true(应该是false)在此示例中,您会看到两个常量a和b具有相同的数值。但是一个是数字,一个是BigInt,用普通的运算符(比如==和>)使用这些东西会导致错误的结果。结论:要让大量数据在应用程序中工作可能需要付出很多努力。因此,最好的做法是首先尽量避免处理这些问题。如果您真的必须处理大值,则必须使用替代的JSON解析器,例如lossless-json。为防止遇到与BigInt或LosslessNumber数据类型相关的难以调试的问题,使用TypeScript显式定义数据模型会很有帮助。这样,您就可以提前知道您需要在何处处理这些特殊数据类型,并且您可以采取行动而不是让您的应用程序默默地失败。在线JSON编辑器现在可以安全地处理大数字截至今天,在线JSON编辑器已完全支持大数字,因此您不必再担心损坏的值。它集成了lossless-json库,并确保编辑器的所有功能都适用于大数字:从格式化、排序和查询到导出到CSV。作为一个副作用,它现在甚至保留了数字的格式,并且由于新的LosslessJSON解析器,现在可以检测到重复键。试一试:https://jsoneditoronline.org/#left=json.%7B%20%22using%22:%20%22Lossless%20JSON%20Parser%22,%20%22formatted%20number%22:%204.0,%20%22long%22:%209123372036854000123,%20%22large%22:%201e500,%20%22small%22:1e-500%20%7D现在,使用lossless-json有一个缺点:它比本机内置JSON。解析要慢得多。这只是大型JSON对象或数组的问题,对于大于10MB的文件可能会很明显。为了顺利处理大文件,JSONEditorOnline允许您选择要使用的解析器,默认情况下,它会自动为您选择最合适的解析器。