Entwicklertools · JSON Formatierer und Validator
Große Ganzzahl-IDs in JSON: Warum JavaScript-Formatierer sie möglicherweise runden
· Warum es wichtig ist
json Entwickler-Workflow Validierung
JSON erlaubt Ganzzahlen jeder Größe, aber JavaScript stellt Zahlen als 64-Bit-Floats dar, sodass sich alles über 2^53 ändern kann, wenn es analysiert und erneut serialisiert wird. In diesem Beitrag wird das Limit erklärt, wie man den Schaden erkennt und wie man Ausweise schützt.
Die ID, die sich um eins geändert hat
Die um eins geänderte ID wird häufig als vollkommen gültig JSON angezeigt. Fügen Sie `{"orderId":9007199254740993}` in JavaScript ein und `JSON.parse` gibt eine Zahl zurück, deren angezeigter Wert `9007199254740992` ist. Das Parsen ist erfolgreich, da das Token der Zahlengrammatik JSON folgt. Der Schaden entsteht bei der Umwandlung dieser Dezimalstellen in die numerische Darstellung von JavaScript. Ein Formatierer, der den analysierten Wert serialisiert, schreibt getreu die gerundete Zahl und nicht das genaue Token, das in der Quelle erschien.
Der Kontrast ist sofort erkennbar, wenn dieselben Ziffern zitiert werden. `JSON.parse("{"orderId":"9007199254740993"}")` gibt die Zeichenfolge `9007199254740993` zurück, wobei jedes Zeichen erhalten bleibt, und `JSON.stringify` gibt diese Ziffern unverändert in Anführungszeichen aus. Aus diesem Grund kann die Syntaxvalidierung allein einen numerischen Bezeichner nicht schützen. Vergleichen Sie Eingabe und Ausgabe immer dann, wenn lange Ganzzahlen auftreten, und behandeln Sie Bezeichner als Zeichenfolgen an der produzierenden Grenze, wenn Arithmetik nicht Teil ihrer Bedeutung ist.
Was RFC 8259 über Zahlen sagt
RFC 8259 definiert die Schreibweise einer JSON-Zahl, gibt aber nicht jeder Implementierung einen numerischen Typ mit beliebiger Genauigkeit. Die Grammatik erlaubt ein optionales Minuszeichen, einen ganzzahligen Anteil sowie optionale Bruch- und Exponentenanteile. Ausgenommen sind Annehmlichkeiten wie die Hexadezimalschreibweise, `NaN` und `Infinity`. Folglich ist `9007199254740993` syntaktisch gültig, auch wenn ein gewöhnlicher JavaScript-Consumer diese Ganzzahl nicht genau als Zahl darstellen kann.
Der Interoperabilitätsleitfaden der Spezifikation ist die praktische Warnung: Software verwendet üblicherweise IEEE-754-Binär64-Zahlen, und ganze Zahlen im Bereich von negativ `2^53 + 1` bis positiv `2^53 - 1` sind im Sinne einer genauen Übereinstimmung interoperabel. Ein Validator kann ein größeres Token korrekt akzeptieren, während ein Parser es später rundet.
Woher 2^53 kommt
Die `2^53`-Grenze ergibt sich aus der Genauigkeit, die in einem binären 64-Signifikanten verfügbar ist. JavaScript stellt die höchste fortlaufend darstellbare Ganzzahl als `Number.MAX_SAFE_INTEGER` bereit, also `9007199254740991`. Bei und unterhalb dieser Größe können benachbarte ganze Zahlen deutlich dargestellt werden. Darüber vergrößert sich der Abstand zwischen darstellbaren Werten, sodass einige benachbarte Dezimalzahlen derselben Zahl zugeordnet werden können. Die Laufzeit schneidet eine Zeichenfolge nicht ab. Es wird der nächstgelegene Wert ausgewählt, der in diesem endlichen Binärformat verfügbar ist.
Eine aufschlussreiche Konsolenprüfung ist `Number.isSafeInteger(9007199254740993)`, was falsch ist, obwohl das Quellliteral bereits gerundet wurde, bevor die Funktion es empfängt. Ein anderes ist `9007199254740992 === 9007199254740993`, das in JavaScript als wahr ausgewertet wird. Diese Beispiele betreffen die exakte Ganzzahlidentität und nicht die Frage, ob jede größere Zahl unbrauchbar wird.
Wie beim Parsen und Reserialisieren Ziffern verloren gehen
Die Parse-and-Reserialize-Formatierung besteht aus drei Phasen: numerische Zeichen lesen, einen Wert im Speicher erstellen und dann neue Zeichen aus diesem Wert generieren. Auf der mittleren Stufe verschwinden lexikalische Details. Mit `{"ticket":9223372036854775807}` erstellt `JSON.parse` die nächstgelegene verfügbare JavaScript-Nummer; `JSON.stringify` gibt dann `9223372036854776000` aus. Der Serialisierer beschädigt ein beibehaltenes Token nicht unabhängig. Zum Zeitpunkt der Serialisierung ist die ursprüngliche Ziffernfolge im analysierten Objekt nicht mehr vorhanden.
Die Repository-Implementierung von ToolAcre verwendet `JSON.parse` und `JSON.stringify`, daher gilt diese Einschränkung für die formatierte Ausgabe. Der Syntaxscanner wird ausgeführt, um einen stabilen Grund und Speicherort bereitzustellen, nachdem die Analyse fehlgeschlagen ist. Es ersetzt keine JavaScript-Zahlen durch eine Darstellung mit beliebiger Genauigkeit. Ein erfolgreiches Validierungsergebnis legt daher die Grammatik fest, während ein Formatierungsunterschied einen Präzisionsverlust aufdecken kann.
Arbeitsbeispiel: Vergleich von Eingabe und Ausgabe
Vergleichen Sie `{"numeric":9007199254740993,"text":"9007199254740993"}` vor und nach einem JavaScript-Roundtrip. Das Ausführen von `JSON.stringify(JSON.parse(source), null, 2)` erzeugt ein formatiertes Objekt, dessen `numeric`-Mitglied `9007199254740992` ist, während `text` `"9007199254740993"` bleibt. Beide Mitglieder waren in der Eingabe gültig und beide bleiben in der Ausgabe gültig. Nur die Darstellung in Anführungszeichen behält den Bezeichner genau bei, da er als Zeichendaten und nicht als Zahl dekodiert wird.
Eine nützliche Überprüfung fragt nicht nur, ob der Formatierer grün angezeigt hat. Durchsuchen Sie die Quelle nach ununterbrochenen Ziffernfolgen, vergleichen Sie alle Werte, die länger als der sichere Bereich sind, und bestimmen Sie, ob jedes Feld eine Menge oder eine undurchsichtige Bezeichnung darstellt. Wenn der Hersteller den Vertrag kontrolliert, ändern Sie dort das Etikett in eine Zeichenfolge und dokumentieren Sie diese Wahl für die Verbraucher.
Schutz von IDs an der Quelle
Schützen Sie IDs an der Quelle, indem Sie sie als Zeichenfolgen im Schema definieren und als Zeichenfolgen serialisieren, bevor ein JavaScript-Client die Nutzdaten empfängt. Eine ID darf nur Ziffern enthalten und dennoch keine numerische Bedeutung haben: Addition, Rundung und Ordnen nach Größe sind keine legitimen Vorgänge an einem Kontoschlüssel. Eine Zeichenfolge behält auch führende Nullen bei, die eine numerische Darstellung auch dann verwerfen würde, wenn ihre Größe im sicheren Bereich liegt.
Schließen Sie die sprachübergreifende Sicherheit nicht aus der Tatsache ab, dass eine andere Laufzeit eine größere Ganzzahl enthalten kann. Parser und Zieltypen variieren, und ein in JavaScript geschriebener Vermittler kann den Wert runden, bevor ein späterer Dienst ihn sieht. Einige spezialisierte Parser behalten Zahlentokens bei oder erstellen große Ganzzahlen, aber jeder Teilnehmer muss diesen Vertrag teilen.
Was dies nicht abdeckt
Was hiermit nicht abgedeckt wird, ist der umfassendere Aufbau der Dezimalarithmetik. Werte wie `0.1` haben ihr eigenes binäres Gleitkommaverhalten, und für Geld sind je nach Anwendungsvertrag möglicherweise skalierte Ganzzahlen oder Dezimaltypen erforderlich. Auch das Zitieren jeder Zahl verbessert ein Schema nicht automatisch. Zählungen, Koordinaten und Messungen sind häufig legitimerweise numerisch. Die Entscheidung hängt davon ab, ob die exakte Dezimalschreibweise oder die exakte Ganzzahlidentität jeden Verbraucher im Datenpfad überleben muss.
Diese Diskussion behauptet auch nicht, dass JSON selbst das Token gerundet hat oder dass sich alle Parser wie JavaScript verhalten. Der konkrete Repository-Beweis ist enger: Dieser Formatierer ruft `JSON.parse` und `JSON.stringify` auf, sodass die JavaScript-Zahlensemantik hier Werte ohne Anführungszeichen regelt. Eine JSON-Bibliothek mit beliebiger Genauigkeit kann unterschiedliche Entscheidungen treffen, muss jedoch definieren, wie Werte verfügbar gemacht und serialisiert werden.
Takeaway: Zahlen über 2^53 gehören in Zeichenfolgen
Die Erkenntnis ist konkret: Integer-Bezeichner außerhalb des sicheren Bereichs von JavaScript gehören in Strings, wenn sie JavaScript passieren müssen, ohne sich zu ändern. `9007199254740993` als JSON-Nummer ist eine gültige Syntax, wird aber nach `JSON.parse` zu `9007199254740992`; `"9007199254740993"` bleibt exakt. Die Zitate sind keine Dekoration. Sie wählen eine Darstellung, die die Ziffern als Daten beibehält und verhindert, dass Verbraucher ein undurchsichtiges Etikett als ungefähre Menge betrachten.
Bevor Sie ein Dokument durch die Ausgabe eines Formatierers ersetzen, vergleichen Sie lange Zahlen mit dem Original und untersuchen Sie jede geänderte Ziffer. Korrigieren Sie nach Möglichkeit den Produzenten und das Schema, damit alle Downstream-Clients das sichere Formular konsistent erhalten. ToolAcre kann die Konsequenz aufdecken, da seine Ausgabe den analysierten JavaScript-Wert widerspiegelt, aber es kann keine Ziffern rekonstruieren, die bereits während der Analyse verloren gegangen sind.