Zum Hauptinhalt springen

JSON-Escaping

JSON-Escaping schreibt Zeichen um, die sonst einen String vorzeitig beenden oder beschädigen würden. RFC 8259 macht zwei Escapes obligatorisch – " und \ –, fügt dann fünf benannte Steuerzeichen-Escapes hinzu, einen optionalen Slash-Escape und die universelle \uXXXX-Form. Dieser Leitfaden behandelt jedes Escape, die Surrogat-Paare hinter Emoji und wann Escaping nützt und wann es schadet.

Nur lokal

JSON-Escaping erklärt

Jeder JSON-String steht zwischen zwei doppelten Anführungszeichen; das Format braucht also eine Möglichkeit, ein doppeltes Anführungszeichen in einen String zu setzen, ohne ihn zu beenden – und einen Backslash in einen String zu setzen, ohne ein Escape zu starten, das so nie gemeint war. Die Antwort ist Escaping: Ein Backslash vor einem Zeichen ändert die Bedeutung dieses Zeichens für den Parser und macht aus einem strukturellen Merkmal gewöhnliche Daten.

Das Schema ist klein und geschlossen. RFC 8259 definiert genau neun zweizeichige Escapes plus eine erweiterbare Form, \uXXXX, und sonst nichts: Ein Backslash, gefolgt von einem beliebigen anderen Zeichen, ist ein Syntaxfehler, kein nachsichtiges Durchlassen. Diese Geschlossenheit ist ein Vorteil – Escaping-Fehler sind immer reproduzierbar, denn ein konformer Parser erkennt die Folge entweder an oder weist das Dokument zurück.

Dieser Leitfaden geht die vollständige Escape-Tabelle durch, danach die beiden Teile, die am schwersten fallen: \uXXXX samt der Surrogat-Paare, die für Emoji nötig sind, und den Größen-Kompromiss beim Escaping von Nicht-ASCII-Text. Alle Beispiele sind kopierbar, und die verlinkten Werkzeuge laufen in Ihrem Browser, sodass Sie Ihre eigenen Strings lokal per Round-Trip prüfen können, ohne etwas zu installieren.

Am schnellsten sieht man Escaping in Aktion, wenn man einem Parser beim Auflösen zusieht – der JSON-Formatter stellt das Ergebnis ohne Escapes neben Ihre Eingabe, während er ein Dokument neu einrückt.

Escaping ist ein Kapitel der Grammatik; den JSON-Syntax-Spickzettel komprimiert den Rest der Regeln – Struktur, Whitespace, Zahlen – auf eine einzige Referenzseite.

Die beiden Escapes, die Sie nie auslassen dürfen

Innerhalb eines Strings muss ein literales doppeltes Anführungszeichen als " geschrieben werden und ein literaler Backslash als \\. Diese beiden sind für jeden Erzeuger obligatorisch, denn ohne sie kann ein Parser nicht erkennen, wo ein String endet oder wo ein Escape beginnt – es gibt keine alternative Schreibweise. Jedes andere Escape der Grammatik ist optional in dem Sinn, dass sein Zeichen auch literal geschrieben werden könnte; diese beiden haben innerhalb eines Strings gar keine literale Form.

Der Schrägstrich ist der Sonderfall: \/ ist legal und bedeutet /, aber der literale Slash muss nie escapt werden. Die Kurzform existiert aus einem historischen Grund – das Einbetten von JSON in HTML-<script>-Blöcke, wo die Folge </script> innerhalb eines Strings das umgebende Tag vorzeitig schließen könnte. Manche Generatoren escapen weiterhin defensiv jeden Slash; der Preis ist ein etwas größeres, etwas unruhigeres Dokument.

Obligatorische und optionale Escapes in einem Dokument

{
  "path": "C:\\temp\\notes.txt",
  "line": "she said \"ship it\"",
  "html": "<\\/script>"
}

Was ein Parser liefert

path:  C:\temp\notes.txt
line:  she said "ship it"
html:  </script>

Die fünf benannten Steuerzeichen

Text darf Steuerzeichen unterhalb von U+0020 enthalten – Tab, Zeilenvorschub, Wagenrücklauf und Verwandte –, aber ein rohes Steuerzeichen innerhalb eines JSON-Strings ist nicht interoperabel: Es bricht zeilenbasierte Werkzeuge und manche Parser komplett. Der Standard verlangt daher, dass Steuerzeichen escapt werden, und benennt fünf von ihnen: \b Backspace (U+0008), \f Form Feed (U+000C), \n Line Feed (U+000A), \r Carriage Return (U+000D) und \t horizontaler Tabulator (U+0009). Jedes andere Steuerzeichen muss die Form \u00XX annehmen, etwa \u0001.

Beachten Sie, was das speziell für Zeilenumbrüche bedeutet: Ein formatiertes Dokument hat echte Zeilenumbrüche zwischen den Tokens, aber ein Zeilenumbruch innerhalb eines String-Werts ist immer die Zeichenfolge \n. Ein String-Wert belegt in der Datei eine einzige Zeile, selbst wenn sein Inhalt mehrzeilig ist – einer der häufigsten Punkte der Verwirrung beim Lesen formatierten JSON.

EscapeCodepunktZeichen
\bU+0008Backspace
\tU+0009horizontaler Tabulator
\nU+000ALine Feed (Zeilenumbruch)
\fU+000CForm Feed
\rU+000DCarriage Return
\u00XXU+0000–U+001Fjedes andere Steuerzeichen

Eine dreizeilige Notiz in einem JSON-String

{
  "note": "first line\nsecond line\ttabbed"
}

Ihr Inhalt nach dem Parsen

first line
second line	tabbed

\uXXXX: jeder Codepunkt, exakt vier Hex-Ziffern

Das \u-Escape schreibt einen Codepunkt als exakt vier Hexadezimalziffern: \u00e9 ist é, \u4f60 ist 你, \u0041 ist dasselbe A, das man auch literal tippen könnte. Parser behandeln die escapte und die literale Form als identisch – ein Dokument, das jedes Nicht-ASCII-Zeichen escapt, und eines, das nur die beiden obligatorischen escapt, dekodieren zu exakt denselben Strings. Escaping ist eine Transportentscheidung, kein Datenunterschied.

Die vier Ziffern begrenzen die Form auf Codepunkte bis U+FFFF, die Basic Multilingual Plane. Alles darüber hinaus – Emoji, historische Schriften, die meisten Symbole – passt nicht in ein einziges \u-Escape, und genau an dieser Begrenzung kommen die Surrogat-Paare ins Spiel.

Zwei Schreibweisen desselben Werts

{
  "city": "\u5317\u4eac",
  "note": "escaped form"
}
{
  "city": "北京",
  "note": "literal UTF-8"
}

Beide parsen zum selben String

city: 北京

(die maskierte Datei ist reines ASCII;
die literale ist kleiner, aber Multibyte)

Astrale Ebene: Surrogat-Paare und einzelne Surrogate

Intern arbeiten JavaScript und die \u-Notation mit UTF-16-Codeeinheiten. Zeichen oberhalb von U+FFFF werden als Paar geschrieben: ein hohes Surrogat aus U+D800–U+DBFF, gefolgt von einem niedrigen Surrogat aus U+DC00–U+DFFF. Das grinsende Gesicht U+1F600 wird deshalb zu \ud83d\ude00 – zwölf Quellzeichen für ein Emoji. Escapen Sie eine Hälfte ohne die andere, entsteht ein einzelnes Surrogat: RFC 8259 §8.2 markiert solche Strings als unvorhersehbar in der Interpretation, und interoperable Erzeuger geben sie schlicht nie aus.

Surrogat-Paare sind auch der Grund, warum die String-Länge lügt. JavaScript meldet die Länge von "😀" als 2, weil es UTF-16-Codeeinheiten zählt statt Zeichen – ein Detail, das zählt, wenn Sie escapten Text schneiden, kürzen oder validieren, und der Grund, warum graphembewusste Bibliotheken existieren.

Ein Emoji, zwei Codeeinheiten, zwölf escapte Zeichen

{
  "mood": "\ud83d\ude00"
}
{
  "mood": "😀"
}

Beide parsen zu demselben einzelnen Zeichen

mood: 😀  (U+1F600 — length 2 in UTF-16 units)

Der Preis des Escapings: Lesbarkeit und Größe

Escaping tauscht Lesbarkeit gegen Sicherheit. Ein Dokument, das jedes Nicht-ASCII-Zeichen escapt, übersteht jeden 7-Bit-Kanal – Gateways, die das achte Bit jedes Bytes abschneiden, Legacy-Mail-Systeme, als pures ASCII konfigurierte Logs – zum Preis fast unleserlicher Texte. Das war die Grundeinstellung vieler früher JSON-Bibliotheken; moderne Voreinstellungen bevorzugen literales UTF-8 und escapen nur, was die Grammatik verlangt.

Der Größenpreis lässt sich aus dem Codepunkt vorhersagen, denn jedes \uXXXX-Escape kostet exakt 6 Bytes, egal was es kodiert. Ein CJK-lastiges Dokument schrumpft durch Unescaping auf etwa die Hälfte; arabischer und kyrillischer Text um rund zwei Drittel; und reines ASCII zu escapen versechsfacht die Größe – deshalb wirken ASCII-escapete Nutzdaten aus älteren Systemen so viel fetter als ihr tatsächlicher Inhalt.

TextCodepunktLiteral in UTF-8Escapte FormEscapte Größe
aU+00611 Byte\u00616 Bytes
بU+06282 Bytes\u06286 Bytes
аU+04302 Bytes\u04306 Bytes
你U+4F603 Bytes\u4f606 Bytes
😀U+1F6004 Bytes\ud83d\ude0012 Bytes

Unescaping, Round-Trips und wann man Escapes behält

Unescaping ist kein separater Schritt, den Sie selbst schreiben müssten: Jeder konforme Parser führt es beim Dekodieren aus, daher liefern das Parsen von "\u0041" und das Parsen von "A" identische Strings. Die umgekehrte Richtung – die Wahl, was beim Erzeugen von JSON escapt wird – ist Geschmackssache der Bibliotheken, und die einzigen harten Regeln dort sind die beiden obligatorischen Escapes plus die Steuerzeichen-Anforderung.

Aggressives Escaping behalten Sie für seine beiden ehrlichen Anwendungsfälle: Kanäle, denen man UTF-8 nicht anvertrauen kann, und Diffs, in denen jede unsichtbare Codierungs-Differenz sichtbar sein soll. Überall sonst ist literales UTF-8 mit minimalen Escapes kleiner, schneller zu scannen und leichter zu prüfen. Sieht ein empfangenes Dokument zu stark escapte aus, parsen Sie es einmal und serialisieren es neu – die Ausgabe legt jedes optionale Escape ab und bleibt exakt dieselben Daten.

Häufig gestellte Fragen

Kann ich vertrauliche Strings gefahrlos in die JSON-Werkzeuge dieser Website einfügen?

Ja – der Formatter, der Validator und der Minifier, die dieser Leitfaden verlinkt, laufen alle lokal in Ihrem Browser. Es gibt keinen Upload-Schritt: Parsen und Neu-Serialisieren geschehen in JavaScript innerhalb des Tabs, das Network-Panel zeigt keine Anfragen mit Ihrem Text, und die Content-Security-Policy der Website blockiert ausgehende Verbindungen als zweite Verteidigungslinie. Ihre Strings verlassen nie die Maschine, auf der Sie sie eingegeben haben.

Wie setze ich ein Emoji in einen JSON-String?

Auf zwei Arten: Fügen Sie das Emoji literal ein – 😀 ist gültiges UTF-8-JSON – oder schreiben Sie sein Surrogat-Paar, \ud83d\ude00. Beide dekodieren zum selben Zeichen. Die escapte Form ist gut zu kennen, weil manche Kanäle und ältere Werkzeuge literale Emoji verunstalten und weil Fehlermeldungen, Logs und Diffs Ihnen oft das Paar statt des Gesichts zeigen.

Was ist ein einzelnes Surrogat und warum bringt es Parser zum Scheitern?

Ein hohes Surrogat (\ud800–\udbff) oder ein niedriges Surrogat (\udc00–\udfff), ohne seinen Partner geschrieben. Allein kodiert es nichts: RFC 8259 §8.2 nennt Strings mit solchen Surrogaten unvorhersehbar in der Interpretation. Die Implementierungen sind unterschiedlich – manche akzeptieren sie, manche ersetzen sie durch U+FFFD, manche werfen einen Fehler –, geben Sie also nie eine halbe Paarhälfte aus und behandeln Sie ein einzelnes Surrogat in eingehenden Daten als Beschädigung.

Warum ist \/ ein gültiges Escape, obwohl / keines braucht?

Es existiert für das Einbetten von JSON in HTML: Innerhalb eines <script>-Blocks würde die literale Folge </script> in einem String den Block vorzeitig beenden, also escapen Generatoren jeden Slash als \/, um das Muster aufzubrechen. Die Grammatik akzeptiert die unescapte Form gleichermaßen. Solange Sie JSON nicht in HTML einbetten, ist \/ reines Rauschen – und Minifizierer entfernen es gerne.

Ändert Escaping die Daten, die meine API erhält?

Nein. Escapes sind Schreibweisen, keine Werte: "\u00e9" und "é" dekodieren zum identischen String, also liefert jeder konforme Parser in beiden Fällen denselben Unicode-Text. Escaping ändert nur, wie die Datei aussieht und wie groß sie ist – deshalb ist eine über-escapte Antwort hässlich, aber harmlos, und Sie können sie normalisieren, indem Sie sie parsen und neu serialisieren.

Wie viel kleiner ist JSON ohne die \u-Escapes?

Das hängt von der Schrift ab, denn jedes \uXXXX kostet 6 Bytes, während literales UTF-8 1 Byte für ASCII kostet, 2 für arabische oder kyrillische Buchstaben, 3 für CJK und 4 für Emoji. Ein chinesisches Dokument schrumpft durch Unescaping auf etwa die Hälfte; Arabisch oder Kyrillisch um rund zwei Drittel; escaptes ASCII wächst um das Sechsfache. Die escapte Form ist immer gleich groß oder größer.

Wo kann ich die hier zitierten Standards nachlesen?

  • RFC 8259 — Das Datenaustauschformat JavaScript Object Notation (JSON)