JSON.parse損壞大數(shù)字的原因解析及解決方案
從10多年前JSON在線編輯器的早期開(kāi)始,用戶(hù)經(jīng)常反映編輯器有時(shí)會(huì)破壞他們JSON文檔中的大數(shù)字的問(wèn)題。直到現(xiàn)在,我們也沒(méi)能解決這個(gè)問(wèn)題。在這篇文章中,我們深入解釋了這個(gè)問(wèn)題,并展示如何在JSON Editor Online中解決這個(gè)問(wèn)題。
大數(shù)字的問(wèn)題
大多數(shù) Web 應(yīng)用程序處理來(lái)自服務(wù)器的數(shù)據(jù)。這些數(shù)據(jù)以純文本的JSON文檔形式被接收,并被解析成一個(gè)JavaScript對(duì)象或數(shù)組,這樣我們就可以讀取屬性并做一些事情。通常情況下,數(shù)據(jù)的解析是使用JSON.parse
函數(shù)進(jìn)行的,該函數(shù)內(nèi)置于JavaScript中,非??焖俸头奖?。
JSON數(shù)據(jù)格式極其簡(jiǎn)單,而且它是JavaScript的一個(gè)子集。所以它與JavaScript完全可以互換。你可以將一個(gè)JSON文檔粘貼到一個(gè)JavaScript文件中,這就是有效的JavaScript。
在JavaScript中使用JSON
應(yīng)該不會(huì)出現(xiàn)任何問(wèn)題,但有一種棘手的情況可能會(huì)破壞數(shù)據(jù):大數(shù)字。這是一個(gè)有效的JSON字符串:
{"count":?9123372036854000123}
當(dāng)我們將其解析為JavaScript并讀取 "count"
鍵時(shí),我們會(huì)得到:
9123372036854000000
解析后的數(shù)值被破壞了:最后三位數(shù)字被重置為零。這是否是一個(gè)問(wèn)題,取決于這些最后的數(shù)字是否確實(shí)有意義,但一般來(lái)說(shuō),知道這種情況可能會(huì)發(fā)生,可能會(huì)給你一種不舒服的感覺(jué)。
為什么大數(shù)字會(huì)被JSON.parse破壞?
像 9123372036854000123
這樣的長(zhǎng)數(shù)字既是有效的 JSON 也是有效的 JavaScript。當(dāng)JavaScript 將數(shù)值解析為數(shù)字時(shí),事情就出錯(cuò)了。最初,JavaScript 只有一種數(shù)字類(lèi)型。Number
。這是一個(gè)64位的浮點(diǎn)值,類(lèi)似于C++、Java或C#中的Double值。這種浮點(diǎn)值可以存儲(chǔ)大約16位數(shù)字。因此,它不能完全代表像9123372036854000123
這樣的數(shù)字,它有19位數(shù)字。在這種情況下,最后三位數(shù)字會(huì)丟失,破壞了該值。
在用浮點(diǎn)數(shù)存儲(chǔ)分?jǐn)?shù)時(shí)也會(huì)發(fā)生同樣的情況:當(dāng)你在 JavaScript 中計(jì)算 1/3
時(shí),結(jié)果是:
0.3333333333333333
在現(xiàn)實(shí)中,該值應(yīng)該有無(wú)限的小數(shù),但 JavaScript 的數(shù)字在大約 16位 之后就停止了。
那么,JSON文檔中像9123372036854000123
這樣的大數(shù)字是怎么來(lái)的呢?嗯,其他語(yǔ)言如Java或C#確實(shí)有其他數(shù)字?jǐn)?shù)據(jù)類(lèi)型,如Long。Long是一個(gè)64
位的值,可以容納最多20位的整數(shù)。它能容納更多數(shù)字的原因是,它不需要像浮點(diǎn)值那樣存儲(chǔ)指數(shù)值。因此,在像Java這樣的語(yǔ)言中,你可以有一個(gè)Long值,它不能在JavaScript的Number類(lèi)型中正確表示,或者在其他語(yǔ)言中的Double類(lèi)型中正確表示。
JavaScript 的 Number(或者更好:任何浮點(diǎn)數(shù)值)還有一些限制:數(shù)值可以溢出或下溢。例如,1e+500
會(huì)變成Infinity
,而1e-500
會(huì)變成0
。不過(guò),這些限制在實(shí)際應(yīng)用程序中很少成為問(wèn)題。
如何防止數(shù)字被 JSON.parse 破壞?
多年來(lái),這個(gè)用 JavaScript 解析大數(shù)字的問(wèn)題一直是https://jsoneditoronline.org/
的用戶(hù)反復(fù)要求的。像大多數(shù)基于網(wǎng)絡(luò)的JSON編輯器一樣,它也使用了本地的JSON.parse函數(shù)和常規(guī)的JavaScript數(shù)字,所以它受到了上述的限制。
第一個(gè)想法可能是:等等,但是 JSON.parse
有一個(gè)可選的reviver
參數(shù),允許你用不同的方式來(lái)解析內(nèi)容。但問(wèn)題是,首先文本被解析成一個(gè)數(shù)字,接下來(lái),它被傳遞給reviver
。所以到那時(shí),已經(jīng)太晚了,值已經(jīng)被破壞了。
為了解決這個(gè)問(wèn)題,根本不能使用內(nèi)置的JSON.parse
,必須使用一個(gè)不同的JSON解析器。對(duì)此有各種優(yōu)秀的解決方案:lossless-json、json-bigint、js-jon-bigint或json-source-map。
這些庫(kù)中的大多數(shù)都采取了務(wù)實(shí)的方法,將長(zhǎng)數(shù)字直接解析為JavaScript相對(duì)較新的BigInt數(shù)據(jù)類(lèi)型。lossless-json
庫(kù)是專(zhuān)門(mén)為JSON Editor Online開(kāi)發(fā)的。它采取了比JSON BigInt解決方案更加靈活和強(qiáng)大的方法。
默認(rèn)情況下,lossless-json 將數(shù)字解析成一個(gè)輕量級(jí)的LosslessNumber
類(lèi),該類(lèi)將數(shù)字值作為一個(gè)字符串持有。這保留了任何數(shù)值,甚至還保留了格式化,比如數(shù)值4.0
中的尾部零。當(dāng)對(duì)其進(jìn)行操作時(shí),LosslessNumber
將被轉(zhuǎn)換為Number
或BigInt
,或者在不安全時(shí)拋出一個(gè)錯(cuò)誤。
該庫(kù)允許你傳遞你自己的數(shù)字解析器,所以你可以應(yīng)用你自己的策略來(lái)處理數(shù)字值。也許你想把長(zhǎng)的數(shù)字值轉(zhuǎn)換成BigInt,或者把數(shù)值傳給某個(gè)BigNumber
庫(kù)。你可以選擇是否要在數(shù)字信息丟失時(shí)拋出一個(gè)異常,或者默默地忽略某些類(lèi)別的信息丟失。
因此,比較本地JSON.parse
函數(shù)和lossless-json
,會(huì)得到以下結(jié)果:
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解析器是否能解決所有問(wèn)題?
答案是并不能。這取決于你在解析數(shù)據(jù)后想做什么,但通常情況下,你想用它做一些事情。在屏幕上顯示數(shù)據(jù),驗(yàn)證它,比較它,排序它,等等。例如,在JSON Editor Online中,你可以編輯數(shù)值,轉(zhuǎn)換文檔(查詢(xún)、過(guò)濾、排序等),比較兩個(gè)文檔,或者根據(jù)JSON模式驗(yàn)證一個(gè)文檔。一旦你引入BigInt值或LosslessNumbers,你想執(zhí)行的所有操作都需要支持這些類(lèi)型的值。
擁有 BigInt 值或 LosslessNumbers 的數(shù)據(jù)很可能給不了解這些數(shù)據(jù)類(lèi)型的第三方庫(kù)帶來(lái)問(wèn)題。例如,JSON Editor Online支持將你的JSON數(shù)據(jù)導(dǎo)出到CSV,并使用優(yōu)秀的json2csv庫(kù)來(lái)實(shí)現(xiàn)。
這個(gè)庫(kù)不知道BigInt
或LosslessNumber
類(lèi)型,不會(huì)正確串聯(lián)這些數(shù)據(jù)類(lèi)型。為了使其正常工作,包含LosslessNumbers
或BigInt
值的JSON數(shù)據(jù)必須首先被轉(zhuǎn)換為該庫(kù)所能理解的數(shù)據(jù)。
即使沒(méi)有第三方庫(kù)的參與,與BigInt值一起工作也會(huì)導(dǎo)致棘手的問(wèn)題。當(dāng)對(duì)大整數(shù)和普通數(shù)字的混合操作時(shí),JavaScript可以默默地將一種數(shù)字類(lèi)型強(qiáng)制轉(zhuǎn)化為另一種,這可能會(huì)導(dǎo)致錯(cuò)誤。下面的代碼例子顯示了這是如何出錯(cuò)的。
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)
在這個(gè)例子中,你看到兩個(gè)常數(shù)a
和b
持有相同的數(shù)字值。但是一個(gè)是數(shù)字,另一個(gè)是BigInt,用這些東西和普通的操作符(如==
和>
)一起使用會(huì)導(dǎo)致錯(cuò)誤的結(jié)果。
結(jié)論:要讓大數(shù)字在一個(gè)應(yīng)用程序中工作,可能需要大量的努力。因此,最好的辦法是盡量避免在一開(kāi)始就處理這些問(wèn)題。
如果你真的要處理大數(shù)值,你必須使用一個(gè)替代的JSON分析器,如lossless-json
。為了防止陷入與擁有BigInt
或LosslessNumber
數(shù)據(jù)類(lèi)型有關(guān)的難以調(diào)試的問(wèn)題,使用TypeScript明確定義你的數(shù)據(jù)模型是很有幫助的。這樣,你就可以事先知道哪些地方需要能夠處理這些特殊的數(shù)據(jù)類(lèi)型,你就可以采取行動(dòng),而不是讓你的應(yīng)用程序默默地失敗。
在線JSON編輯器現(xiàn)在可以安全地處理大數(shù)字了
從今天起,JSON Editor Online已經(jīng)完全支持大數(shù)字,所以你不必再擔(dān)心損壞的數(shù)值。它已經(jīng)集成了lossless-json
庫(kù),并確保編輯器的所有功能都能處理大數(shù)字:從格式化、排序和查詢(xún)到導(dǎo)出到CSV。作為一個(gè)副作用,它現(xiàn)在甚至保持了數(shù)字的格式化,而且由于新的LosslessJSON
解析器,現(xiàn)在可以檢測(cè)到重復(fù)的鍵。
試一試:點(diǎn)擊參考鏈接
現(xiàn)在,使用lossless-json
有一個(gè)缺點(diǎn):它比原生內(nèi)置的JSON.parse
慢得多。這只是大的JSON對(duì)象或數(shù)組的問(wèn)題,對(duì)于大于10MB的文件,它可能會(huì)很明顯。為了仍能順利地處理大文件,JSON Editor Online允許你選擇你想使用的解析器,默認(rèn)情況下,它會(huì)自動(dòng)為你選擇最合適的解析器。
到此這篇關(guān)于為什么JSON.parse會(huì)損壞大數(shù)字,如何解決這個(gè)問(wèn)題?的文章就介紹到這了,更多相關(guān)JSON.parse損壞大數(shù)字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js判斷手機(jī)是否安裝并打開(kāi)app,未安裝則安裝app【兼容Android、ios,親測(cè)可用】
這篇文章主要介紹了js判斷手機(jī)是否安裝并打開(kāi)app,未安裝則安裝app,通過(guò)調(diào)用瀏覽器判斷app,兼容Android、ios等系統(tǒng),,需要的朋友可以參考下2023-05-05js中獲取鍵盤(pán)事件的簡(jiǎn)單實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js中獲取鍵盤(pán)事件的簡(jiǎn)單實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10document.body.scrollTop 值總為0的解決方法 比較常見(jiàn)的標(biāo)準(zhǔn)問(wèn)題
頁(yè)面具有 DTD(或者說(shuō)指定了 DOCTYPE)時(shí),使用 document.documentElement。2009-11-11IE8中動(dòng)態(tài)創(chuàng)建script標(biāo)簽onload無(wú)效的解決方法
這篇文章主要介紹了IE8中動(dòng)態(tài)創(chuàng)建script標(biāo)簽onload無(wú)效的解決方法,涉及針對(duì)javascript加載順序的調(diào)整,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12js實(shí)現(xiàn)自動(dòng)播放勻速輪播圖
這篇文章主要介紹了js實(shí)現(xiàn)自動(dòng)播放勻速輪播圖,帶勻速運(yùn)動(dòng)函數(shù)封裝,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02JS實(shí)現(xiàn)的簡(jiǎn)單圖片切換功能示例【測(cè)試可用】
這篇文章主要介紹了JS實(shí)現(xiàn)的簡(jiǎn)單圖片切換功能,結(jié)合實(shí)例形式分析了javascript結(jié)合時(shí)間函數(shù)定時(shí)觸發(fā)控制圖片的遍歷與切換操作相關(guān)技巧,需要的朋友可以參考下2017-02-02詳解webpack 多頁(yè)面/入口支持&公共組件單獨(dú)打包
這篇文章主要介紹了詳解webpack 多頁(yè)面/入口支持&公共組件單獨(dú)打包,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06JavaScript判斷對(duì)象、數(shù)組是否包含某個(gè)屬性、某個(gè)值的方法
這篇文章主要給大家介紹了關(guān)于JavaScript判斷對(duì)象、數(shù)組是否包含某個(gè)屬性、某個(gè)值的相關(guān)資料,我們?cè)趯?shí)際的開(kāi)發(fā)過(guò)程中,經(jīng)常需要判斷對(duì)象/數(shù)組是否包含某個(gè)屬性或者某個(gè)值,需要的朋友可以參考下2023-09-09