開発者ツール · JSON フォーマッタおよびバリデータ
JSON の大きな整数 ID: JavaScript フォーマッタがそれらを丸める理由
· なぜそれが重要なのか
json 開発者ワークフロー 検証
JSON では任意のサイズの整数が許可されますが、JavaScript は数値を 64 ビット浮動小数点として表現するため、 2^53 を超えるものは、解析および再シリアル化すると変更される可能性があります。この記事では、制限、被害の見分け方、ID の保護方法について説明します。
1つ変わったID
1 つ変更された ID は、完全に有効な JSON として届くことがよくあります。 `{"orderId":9007199254740993}` を JavaScript に入力すると、`JSON.parse` は表示値が `9007199254740992` である数値を返します。トークンは JSON 数値文法に従っているため、解析は成功します。損傷は、これらの 10 進数を JavaScript の数値表現に変換するときに発生します。解析された値をシリアル化するフォーマッタは、ソースに出現した正確なトークンではなく、丸められた数値を忠実に書き込みます。
同じ数字を引用すると、コントラストがすぐに現れます。 `JSON.parse("{"orderId":"9007199254740993"}")` は、すべての文字を保持した文字列 `9007199254740993` を返し、`JSON.stringify` は引用符内でそれらの数字を変更せずに出力します。これが、構文検証だけでは数値識別子を保護できない理由です。長整数が出現するたびに入力と出力を比較し、算術演算が意味の一部でない場合は、生成境界で識別子を文字列として扱います。
RFC 8259 が数値について述べていること
RFC 8259 は JSON 数値の綴りを定義していますが、すべての実装に任意精度の数値型を与えるわけではありません。文法では、オプションのマイナス記号、整数部分、およびオプションの小数部と指数部分が許可されています。 16 進表記、`NaN`、`Infinity` などの便利な機能は除外されます。したがって、一般的な JavaScript コンシューマーがその整数を数値として正確に表現できない場合でも、`9007199254740993` は構文的には有効です。
仕様の相互運用性に関するガイダンスは実際的な警告です。ソフトウェアは通常、IEEE 754 binary64 数値を使用しており、負の `2^53 + 1` から正の `2^53 - 1` までの範囲の整数は、正確に一致するという意味で相互運用可能です。バリデーターは、後でパーサーがそれを四捨五入する間、より大きなトークンを正しく受け入れることができます。
2^53 の由来
`2^53` 境界は、binary64 仮数部で利用可能な精度に由来します。 JavaScript は、連続して表現可能な最大の整数を `Number.MAX_SAFE_INTEGER` として公開します。これは `9007199254740991` です。その大きさ以下では、隣接する整数を明確に表現できます。それを超えると、表現可能な値間の間隔が広がるため、一部の隣接する 10 進整数が同じ数値にマッピングされます。ランタイムは文字列を切り捨てません。それは、その有限のバイナリ形式で利用可能な最も近い値を選択することです。
明らかなコンソール チェックは `Number.isSafeInteger(9007199254740993)` であり、関数が受け取る前にソース リテラルがすでに丸められていますが、これは false です。もう 1 つは `9007199254740992 === 9007199254740993` で、JavaScript で true と評価されます。これらの例は、整数の正確な同一性に関するものであり、より大きな数値がすべて使用できなくなるかどうかに関するものではありません。
解析と再シリアル化で桁が失われる仕組み
解析と再シリアル化のフォーマットには 3 つの段階があります。数値文字の読み取り、メモリ内値の作成、その値からの新しい文字の生成です。語彙の詳細は中盤で消えます。 `{"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 の保護
JavaScript クライアントがペイロードを受信する前に、スキーマ内で ID を文字列として定義し、文字列としてシリアル化することで、ソースで ID を保護します。 ID には数字のみが含まれている場合がありますが、意味としては非数値のままです。加算、丸め、および大きさによる並べ替えは、アカウント キーに対する正当な操作ではありません。文字列では先頭のゼロも保持されますが、数値表現では、その大きさが安全な範囲内にある場合でも、先頭のゼロは破棄されます。
別のランタイムがより大きな整数を保持できるという事実から、言語間の安全性を推測しないでください。パーサーとターゲットのタイプはさまざまで、JavaScript で書かれた仲介者は、後のサービスが値を認識する前に値を丸めることができます。一部の特殊なパーサーは数値トークンを保存したり、大きな整数を構築したりしますが、すべての参加者がそのコントラクトを共有する必要があります。
これでカバーされない内容
これでは、10 進演算の広範な設計については説明しません。 `0.1` などの値には独自のバイナリ浮動小数点動作があり、アプリケーション契約に従って通貨にはスケーリングされた整数または 10 進数タイプが必要になる場合があります。また、すべての数値を引用しても自動的にスキーマが改善されるわけではありません。カウント、座標、測定値は多くの場合、正当な数値です。この決定は、正確な 10 進数のスペルまたは正確な整数の ID がデータ パス内のすべてのコンシューマに存続する必要があるかどうかによって異なります。
この議論では、JSON 自体がトークンを四捨五入したり、すべてのパーサーが JavaScript のように動作するとも主張しません。具体的なリポジトリの証拠はより限定的です。このフォーマッタは `JSON.parse` および `JSON.stringify` を呼び出すため、ここでは JavaScript 数値セマンティクスが引用符で囲まれていない値を制御します。任意精度の JSON ライブラリではさまざまな選択が可能ですが、値の公開方法とシリアル化方法を定義する必要があります。
要点: 2^53 より上の数値は文字列に属します
要点は具体的です。JavaScript の安全範囲外の整数識別子は、変更せずに JavaScript を通過する必要がある場合、文字列に属します。 JSON 数値としての `9007199254740993` は有効な構文ですが、 `JSON.parse` の後には `9007199254740992` になります。 `"9007199254740993"` は正確なままです。引用符は飾りではありません。彼らは、数字をデータとして保存し、消費者が不透明なラベルをおおよその量として扱うことを防ぐ表現を選択します。
ドキュメントをフォーマッタ出力で置き換える前に、長い数値を元の数値と比較し、変更されたすべての桁を調査します。可能な場合はプロデューサーとスキーマを修正して、すべてのダウンストリーム クライアントが一貫して安全なフォームを受信できるようにします。 ToolAcre は、その出力が解析された JavaScript 値を反映しているため、結果を明らかにすることができますが、解析中にすでに失われた数字を再構築することはできません。