為什麼JSON.parse會損壞大數字,如何解決這個問題?

前端小智發表於2023-04-27
微信搜尋 【大遷世界】, 我會第一時間和你分享前端行業趨勢,學習途徑等等。
本文 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 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章