JSON-Grammatikgrenzen aus RFC 8259 lesen
Fazit: Die Grammatikproduktionen von RFC 8259 in klare Sprache übersetzt: welche Zeichen direkt vorkommen dürfen, welche escaped werden müssen und wo die Spezifikation bewusst Spielraum lässt.

Kurz gesagt: Wenn ein JSON-Syntaxfehler auftritt, suche das erste unzulässige Zeichen und ordne es als einfaches Anführungszeichen, nachfolgendes Komma, Kommentar oder Steuerzeichen ein, statt das ganze Dokument neu zu lesen; auf der Kodierungsseite bestätige UTF-8 ohne BOM und führe Ganzzahlen oberhalb von 2^53 - 1 als Strings.
Der Text der Spezifikation umfasst nur ein Dutzend Seiten, doch hinter jedem „MUST“, „SHOULD“ und „MAY“ verbirgt sich ein Vorfall, der ein Team einst die ganze Nacht wachgehalten hat.
1. Wie die drei Texte zusammenhängen
Das Erlernen von JSON wird oft durch drei Nummern verwirrt: RFC 4627, RFC 7159, RFC 8259 und dazu ECMA-404. Der Zusammenhang ist eigentlich ganz klar:
| Text | Datum | Status | Wesentlicher Unterschied |
|---|---|---|---|
| RFC 4627 | 2006 | Abgelöst | Die oberste Ebene muss ein Objekt oder ein Array sein |
| RFC 7159 | 2014 | Abgelöst | Oberste Ebene auf jeden JSON-Wert gelockert |
| RFC 8259 | 2017.12 | Aktueller Standard | Explizit UTF-8, Interoperabilitätshinweise ergänzt |
| ECMA-404 | 2013, zweite Auflage 2017.12 | Grammatikspezifikation | Definiert nur die Grammatik, nicht Kodierung oder Transport |
Welchen sollte man lesen? RFC 8259 allein genügt. ECMA-404 ist im Grammatikteil gleichwertig; der Unterschied besteht darin, dass RFC 8259 zusätzlich „Transport als UTF-8“ und „die Interoperabilitätsprobleme, auf die Implementierer achten sollten“ festlegt.
2. Sechs Werttypen
Die Grammatikproduktionen der Spezifikation lassen sich in einem einzigen Schema zusammenfassen:
JSON-text = ws value ws
value = object / array / number / string / "true" / "false" / "null"
object = "{" [ member *( "," member ) ] "}"
member = string ws ":" ws value
array = "[" [ value *( "," value ) ] "]"
Es gibt nur sechs Kernpunkte:
- Objekte verwenden geschweifte Klammern, die Mitglieder werden durch Kommas getrennt, und ein Komma nach dem letzten Mitglied ist nicht erlaubt;
- Arrays verwenden eckige Klammern, die Elemente werden durch Kommas getrennt, und nachfolgende Kommas sind ebenfalls nicht erlaubt;
- Zahlen erlauben kein führendes
+, keine führenden Nullen (01ist ungültig), keinen bloßen Dezimalpunkt (1.ist ungültig) und keinNaNoderInfinity; - Strings müssen doppelte Anführungszeichen verwenden; einfache Anführungszeichen sind ungültig;
- Booleans und null sind Literale in Kleinbuchstaben;
TrueundNULLsind beide ungültig; - Nur Whitespace-Zeichen
0x20(Leerzeichen),0x09(Tabulator),0x0A(Zeilenvorschub) und0x0D(Wagenrücklauf) dürfen zwischen Strukturelementen stehen.
Das folgende Fragment ist gültiges JSON, obwohl viele es beim ersten Anblick für falsch halten – weil es ein nackter String auf oberster Ebene ist:
"just a string"
Seit RFC 7159 kann die oberste Ebene tatsächlich jeder Wert sein. In APIs empfiehlt es sich dennoch, nur Objekte oder Arrays zu verwenden; der Grund steht in Abschnitt 6.
3. Was innerhalb eines Strings tatsächlich direkt stehen darf
Dies ist der Teil, bei dem „läuft auf meiner Maschine doch einwandfrei“ am häufigsten täuscht. Die Regeln der Spezifikation für Strings lauten:
- Direkt erlaubte Zeichen:
%x20-21,%x23-5B,%x5D-10FFFF; - Anders gesagt: das doppelte Anführungszeichen
"(0x22) und der Backslash\(0x5C) müssen escaped werden; - Alle Steuerzeichen unterhalb von 0x20 müssen escaped werden, einschließlich Zeilenvorschub, Tabulator und Wagenrücklauf;
- Als Escape-Sequenzen stehen ausschließlich
\",\\,\/,\b,\f,\n,\r,\tund\uXXXXzur Verfügung.
{
"ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
"escaped": "A line break goes here\nand a tab\tfinishes it",
"control": "\u0007 is the bell character and must be escaped",
"quote": "He said: \"don't use single quotes\""
}
\/ ist ein Altlast-Escape (es verhindert, dass </script> vorzeitig schließt). Moderne Szenarien brauchen es selten, aber Parser müssen es unterstützen.
4. Kodierung: UTF-8, kein BOM
Abschnitt 8.1 von RFC 8259 sagt drei Dinge:
- Für den Austausch verwendeter JSON-Text muss in UTF-8 kodiert sein;
- Implementierungen dürfen am Anfang des Textes kein BOM (
EF BB BF) hinzufügen; - Parser dürfen das BOM ignorieren, um robuster zu sein.
Zwei typische Fehler aus der Praxis:
- Der Windows-Editor speichert als „UTF-8 mit BOM“, die Datei wird von
JSON.parsemitUnexpected tokenabgelehnt; - Eine chinesische JSON-Datei, als GBK gespeichert, wird auf einer Pipeline, die nur UTF-8 dekodiert, zu Kauderwelsch, während die Syntax zufällig noch durchgeht – der Fehler wird also in die Geschäftslogik verschoben.
Die Behandlung ist schlicht: Beim Lesen der Datei das BOM entfernen und dann ausdrücklich als UTF-8 dekodieren; beim Schreiben sicherstellen, dass kein BOM hinzugefügt wird.
/**
* Removes the UTF-8 BOM at the start of a string.
* @param text The original text
* @returns The text without a BOM
*/
function stripBom(text: string): string {
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}
Noch ein Hinweis zu Medientypen: application/json akzeptiert keinen charset-Parameter mehr; schreibt man einen, entsteht kein Fehler, aber es ist eine veraltete Praxis.
5. Drei Stellen, an denen die Spezifikation bewusst Spielraum lässt
Genau dort, wo die Spezifikation es versäumt, etwas festzulegen, entstehen Implementierungsunterschiede.
1. Wertebereich und Präzision von Zahlen
RFC 8259 sagt nur, „diese Spezifikation begrenzt Wertebereich und Präzision von Zahlen nicht“, und merkt an, dass die meisten Implementierungen IEEE 754 doppelte Präzision verwenden.
Das bedeutet, dass dieselbe Zahl 9223372036854775807 in JavaScript zu 9223372036854776000 wird, in Javas long aber exakt bleibt.
Fazit: Ganzzahlen jenseits von 2^53 - 1 und Geldbeträge, die viele Nachkommastellen Präzision benötigen, sollten stets als Strings geführt werden, mit ausdrücklich im Schema angegebenem pattern.
2. Doppelte Schlüssel
Die Formulierung der Spezifikation lautet: „Die Namen innerhalb eines Objekts sollten (SHOULD) eindeutig sein“; was bei Nichteindeutigkeit zu tun ist, sagt sie nicht.
JSON.parsebehält den letzten;- Manche Parser behalten den ersten;
- Manche Bibliotheken werfen schlicht einen Fehler.
Fazit: Keine doppelten Schlüssel schreiben und sich in Validatoren nicht auf ein bestimmtes Verhalten verlassen. Einen Schritt „Erkennung doppelter Schlüssel“ zur Build-Zeit einzubauen ist weit günstiger, als danach zu untersuchen.
3. Grenzen für Tiefe und Länge
Die Spezifikation setzt keine Grenze für die Verschachtelungstiefe, aber jede Implementierung hat eine. V8 wirft bei Zehntausenden Verschachtelungsebenen RangeError: Maximum call stack size exceeded.
Server müssen eigene Grenzen setzen; das ist eine Sicherheitsanforderung, keine Anforderung der Spezifikation.
6. Das Verständnis mit Gegenbeispielen prüfen
Jede Zeile unten ist ungültiges JSON. Gib sie einem Parser und prüfe, ob die Fehlermeldungen hilfreich sind:
{ 'name': 'json' } // single quotes
{ name: "json" } // key name not quoted
{"a": 1,} // trailing comma
[1, 2, 3,] // trailing comma in an array
{"a": 01} // leading zero
{"a": +1} // leading plus sign
{"a": .5} // missing integer part
{"a": NaN} // non-standard literal
{"a": undefined} // not a JSON value
{"a": 1} // comment // comment
Ein interessanter Grenzfall: {"a": 1} gefolgt von einem Kommentar ist in JSON5 gültig, in JSONC gültig und in Standard-JSON ungültig.
Dass „die Konfigurationsdatei läuft, aber die API lehnt sie ab“, ist also fast immer ein Dialektproblem.
7. Checkliste für die Produktion
- [ ] Alle extern übertragenen JSON-Daten verwenden
application/jsonohnecharset; - [ ] Pipelines zum Lesen und Schreiben von Dateien entfernen das BOM und verwenden ausdrücklich UTF-8;
- [ ] Ganzzahlen jenseits von
2^53werden als Strings geführt und im Schema vermerkt; - [ ] Doppelte Schlüssel werden zur Build-Zeit erkannt;
- [ ] Der Server begrenzt Anfragegröße und Verschachtelungstiefe;
- [ ] Die obersten Werte öffentlicher APIs sind Objekte oder Arrays, nie nackte Skalare.
FAQ
Wie erkenne ich schnell, ob ein Text gültiges JSON ist?
Gib den Text einem Parser oder einem Online-Parsing-Werkzeug, lies die Position des ersten unzulässigen Zeichens und prüfe sie gegen die sechs Werttypen und Whitespace-Regeln von RFC 8259. Die oberste Ebene darf jeder JSON-Wert sein, aber zwischen Strukturelementen sind nur Leerzeichen, Tabulator, Zeilenvorschub und Wagenrücklauf erlaubt. Ein einfaches Anführungszeichen, ein unquotierter Schlüssel, ein nachfolgendes Komma oder ein Kommentar bedeutet: kein Standard-JSON.
Warum muss JSON UTF-8 verwenden und darf kein BOM tragen?
Abschnitt 8.1 von RFC 8259 verlangt, dass für den Austausch verwendeter JSON-Text in UTF-8 kodiert ist, legt fest, dass Implementierungen am Anfang kein BOM hinzufügen dürfen, und erlaubt Parsern, das BOM aus Robustheitsgründen zu ignorieren. Eine Datei mit BOM wird von JSON.parse mit Unexpected token abgelehnt, obwohl optisch nichts falsch aussieht. Also beim Lesen das BOM entfernen und ausdrücklich als UTF-8 dekodieren.
Sollte ich diese Grammatikgrenzen mit JSON5 oder JSONC umgehen?
Ja, aber nur für lokales Lesen etwa von Konfigurationsdateien; zwischen Systemen ausgetauschte Daten sollten weiterhin Standard-JSON sein. Die Gegenbeispiele zeigen, dass {"a": 1} gefolgt von einem Kommentar in JSON5 und JSONC gültig, in Standard-JSON aber ungültig ist – deshalb ist „die Konfiguration läuft, aber die API lehnt sie ab“ fast immer ein Dialektproblem. Beim Konvertieren zuerst Kommentare, einfache Anführungszeichen und nachfolgende Kommas entfernen und das Ergebnis einem Standardparser übergeben.