繁體中文

開發者工具 · JSON 格式化程式和驗證程序

JSON 中的大整數 ID:為什麼 JavaScript 格式化程式可能會對它們進行四捨五入

· 為什麼它很重要

json 開發人員工作流程 驗證

JSON 中的大整數 ID:為什麼 JavaScript 格式化程式可能會對它們進行四捨五入,並用 JSON 標記和精確的驗證邊界進行說明
原始 ToolAcre 向量圖

JSON 允許任何大小的整數,但 JavaScript 將數字表示為 64 位元浮點數,因此任何高於 2^53 的內容在解析和重新序列化時都會發生變化。這篇文章解釋了限制、如何發現損壞以及如何保護 ID。

改了 1 的 ID

改變了 1 的 ID 通常會作為完全有效的 JSON 到達。將 `{"orderId":9007199254740993}` 放入 JavaScript 中,`JSON.parse` 傳回一個數字,其顯示值為 `9007199254740992`。解析成功,因為令牌遵循 JSON 數字語法;當將這些十進制數字轉換為 JavaScript 的數字表示形式時,就會發生損壞。序列化解析值的格式化程式會忠實地寫入四捨五入的數字,而不是來源中出現的確切標記。

當引用相同的數字時,對比是立即的。 `JSON.parse("{"orderId":"9007199254740993"}")` 傳回字串 `9007199254740993`,保留每個字符,而 `JSON.stringify` 在引號內原封不動地發出這些數字。這就是為什麼單獨的語法驗證無法保護數字標識符。每當出現長整數時就比較輸入和輸出,並當算術不是其含義的一部分時,將標識符視為產生邊界處的字串。

RFC 8259 關於數字的說法

RFC 8259 定義了 JSON 數字的拼寫,但並未為每個實作提供任意精確度的數字類型。此語法允許可選的減號、整數部分以及可選的分數和指數部分。它不包括十六進位表示法、`NaN` 和 `Infinity` 等方便的符號。因此,即使常見的 JavaScript 使用者無法將該整數精確地表示為數字,`9007199254740993` 在語法上也是有效的。

規範的互通性指導是實際警告:軟體通常使用 IEEE 754 二進位 64 數字,並從負 `2^53 + 1` 到正 `2^53 - 1` 範圍內的整數在精確一致的意義上是可互操作的。驗證器可以正確接受較大的標記,而解析器稍後會對其進行舍入。

2^53 來自哪裡

`2^53` 邊界來自二進位 64 尾數中可用的精度。 JavaScript 將可連續表示的最高整數公開為 `Number.MAX_SAFE_INTEGER`,即 `9007199254740991`。在該大小或低於該大小時,可以清楚地表示相鄰整數。在其上方,可表示數值之間的間距增大,因此一些相鄰的十進制整數會對應到相同的數字。執行時不會截斷字串;它正在選擇該有限二進位格式中可用的最接近的值。

一個揭示性的控制台檢查是 `Number.isSafeInteger(9007199254740993)`,它是錯誤的,儘管源文字在函數接收它之前已經被舍入。另一個是 `9007199254740992 === 9007199254740993`,它在 JavaScript 中評估為 true。這些範例涉及精確的整數標識,而不是每個較大的數字是否變得不可用。

解析與重新序列化如何遺失數字

解析並重新序列化格式化分為三個階段:讀取數字字符,建立記憶體中值,然後從該值產生新字符。詞彙細節在中間階段消失。使用 `{"ticket":9223372036854775807}`,`JSON.parse` 建立最接近的可用 JavaScript 編號; `JSON.stringify` 然後發出 `9223372036854776000`。序列化器不會獨立地破壞保留的令牌。到序列化時,原始數字序列不再存在於解析的物件中。

ToolAcre 的儲存庫實作使用 `JSON.parse` 和 `JSON.stringify`,因此此限制適用於其格式化輸出。其語法掃描器執行以在解析失敗後提供穩定的原因和位置;它不會用任意精度的表示形式替換 JavaScript 數字。因此,成功的驗證結果可以建立語法,而格式差異可以揭示精度損失。

工作範例:比較輸入和輸出

比較 JavaScript 往返前後的 `{"numeric":9007199254740993,"text":"9007199254740993"}`。執行 `JSON.stringify(JSON.parse(source), null, 2)` 會產生一個格式化對象,其 `numeric` 成員為 `9007199254740992`,而 `text` 仍為 `"9007199254740993"`。兩個成員在輸入中均有效,並在輸出中仍然有效。只有引號的表示形式才能準確保留標識符,因為它被解碼為字元資料而不是數字。

有用的評論不僅詢問格式化程式是否顯示綠色。在來源中搜尋不間斷的數字序列,比較任何超出安全範圍的值,並確定每個欄位是否代表數量或不透明標籤。如果生產者控制合同,請將標籤變更為字串,並為消費者記錄該選擇。

從源頭保護 ID

透過在架構中將 ID 定義為字串並在任何 JavaScript 用戶端接收有效負載之前將其序列化為字串,從來源保護 ID。 ID 可能只包含數字,但在意義上仍然是非數字:加法、舍入和按大小排序不是帳戶密鑰上的合法操作。字串也保留前導零,即使其大小在安全範圍內,數字表示也會丟棄這些前導零。

不要根據另一個執行時可以容納更大整數的事實來推斷跨語言安全性。解析器和目標類型各不相同,並用 JavaScript 編寫的中介可以在後續服務看到該值之前對其進行舍入。一些專門的解析器保留數字標記或構造大整數,但每個參與者都必須共享該合約。

這不包括什麼

這不包括更廣泛的十进制算術設計。諸如 `0.1` 之類的值有自己的二進制浮點行為,並根據應用程式合約,貨幣可能需要縮放整數或小數類型。引用每個數字也不會自動改善模式。計數、座標和測量值通常是合法的數字。該決定取決於精確的十進制拼字或精確的整數標識是否必須保留在資料路徑中的每個消費者中。

此討論也沒有聲稱 JSON 本身對標記進行四捨五入,或者所有解析器的行為都類似於 JavaScript。具體的儲存庫證據更窄:此格式化程式呼叫 `JSON.parse` 和 `JSON.stringify`,因此 JavaScript 數字語意控制此處未加引號的值。任意精度 JSON 函式庫可以做出不同的選擇,但它必須定義如何公開和序列化值。

重點: 2^53 以上的數字屬於字串

重點是具體的:當 JavaScript 安全範圍之外的整數標識符必須在不改變的情況下通過 JavaScript 時,它們就屬於字串。 `9007199254740993` 作為 JSON 數字是有效語法,但在 `JSON.parse` 之後變為 `9007199254740992`; `"9007199254740993"` 保持準確。引號不是裝飾。他們選擇一種將數字保留為資料的表示形式,並防止消費者將不透明的標籤視為近似數量。

在使用格式化程式輸出取代檔案之前,請將長數字與原始數字進行比較並調查每個變更的數字。盡可能修復生產者和模式,以便所有下游用戶端一致地接收安全表單。 ToolAcre 可以公開結果,因為它的輸出反映了解析後的 JavaScript 值,但它無法重建在解析過程中已經遺失的數字。