Sonderzeichen in JSON: Anführungszeichen, Umlaute und Zeilenumbrüche escapen

In einem JSON-Text müssen drei Arten von Zeichen escaped werden: das Anführungszeichen " als \", der Backslash \ als \\ und alle Steuerzeichen unterhalb von U+0020, zum Beispiel Zeilenumbruch als \n und Tab als \t. Alles andere darfst du direkt schreiben, auch Umlaute und Emoji. Sonst meldet der Parser einen Fehler wie Bad escaped character oder Bad control character in string literal.

Wenn du nicht sicher bist, wo in deinem JSON ein Zeichen falsch escaped ist, füge den Text in den JSON-Formatierer ein. Er zeigt Zeile und Spalte der Fehlerstelle auf Deutsch. Die Verarbeitung läuft komplett lokal im Browser, es wird nichts an einen Server gesendet.

Die Escape-Tabelle

Nach RFC 8259 (und ECMA-404) beginnt jede Escape-Sequenz mit einem Backslash. Es gibt genau diese:

Sequenz Bedeutung
\" Anführungszeichen
\\ Backslash
\/ Schrägstrich (optional)
\b Backspace (U+0008)
\f Seitenvorschub (U+000C)
\n Zeilenumbruch (U+000A)
\r Wagenrücklauf (U+000D)
\t Tabulator (U+0009)
\uXXXX Zeichen mit dem Unicode-Codepoint XXXX (genau vier Hex-Ziffern)

\/ ist nie nötig: "a/b" und "a\/b" bedeuten dasselbe. Es taucht vor allem in JSON auf, das in HTML eingebettet wird, damit die Zeichenfolge </script> nicht versehentlich ein Script-Element beendet. Beim Einlesen wird es einfach zu /.

Mehr Sequenzen gibt es nicht. Insbesondere sind \', \x41, \0, \v und \a in JSON ungültig, auch wenn JavaScript, C oder Python sie kennen.

Steuerzeichen müssen escaped werden

Jedes Zeichen von U+0000 bis U+001F darf in einem JSON-Text nicht roh vorkommen. Das betrifft vor allem Zeilenumbrüche und Tabs, die du aus einem mehrzeiligen Text oder einem Textfeld kopierst:

{ "text": "Zeile 1
Zeile 2" }

Chrome und Node.js melden hier Bad control character in string literal, Python Invalid control character at. Richtig ist:

{ "text": "Zeile 1\nZeile 2" }

Hast du keine Kurzform wie \n, schreibst du das Zeichen als \u plus Codepoint, etwa \u0001 für U+0001. Das gilt auch für unsichtbare Zeichen, die aus Copy-and-paste stammen können. Ein Zeilenumbruch zwischen Einträgen ist dagegen erlaubt: Whitespace außerhalb von Texten (Leerzeichen, Tab, \n, \r) ist normale Formatierung.

Umlaute: UTF-8 oder \u00e4

RFC 8259 schreibt UTF-8 für JSON vor, das zwischen Systemen ausgetauscht wird. Umlaute, ß und andere Zeichen darfst du deshalb direkt schreiben:

{ "ort": "Düsseldorf", "straße": "Königsallee" }

Gleichwertig, aber schlechter lesbar:

{ "ort": "D\u00fcsseldorf", "stra\u00dfe": "K\u00f6nigsallee" }

Beide Schreibweisen ergeben nach dem Parsen denselben Text. Die \u-Form ist nützlich, wenn eine Übertragung nur ASCII sicher transportiert. Wenn dagegen in der Anzeige ä statt ä erscheint, liegt das nicht am Escaping, sondern an einer falsch gelesenen Kodierung: Die UTF-8-Datei wurde als Latin-1 oder Windows-1252 interpretiert. Dann hilft es, die Datei mit der richtigen Kodierung zu öffnen. Ein unsichtbares Zeichen am Dateianfang behandelt der Ratgeber BOM in JSON.

Pythons json.dumps() schreibt standardmäßig \u-Sequenzen für alles außerhalb von ASCII. Mit ensure_ascii=False bekommst du lesbare Zeichen:

import json

json.dumps({"s": "Straße"})                       # {"s": "Stra\u00dfe"}
json.dumps({"s": "Straße"}, ensure_ascii=False)   # {"s": "Straße"}

Emoji und Surrogatpaare

\uXXXX hat nur vier Hex-Ziffern und deckt damit Codepoints bis U+FFFF ab. Zeichen darüber, etwa die meisten Emoji, schreibst du als Surrogatpaar aus zwei \u-Sequenzen. Aus 😀 (U+1F600) wird:

{ "emoji": "\ud83d\ude00" }

Direkt als UTF-8 geschrieben ("😀") ist es genauso gültig und im Regelfall die bessere Wahl. Wichtig ist nur, dass ein Paar immer zusammenbleibt. Ein einzelnes \ud83d ist laut Standard syntaktisch zwar erlaubt, ergibt aber keinen gültigen Unicode-Text. Programme gehen damit unterschiedlich um. Erzeugst du JSON mit einer Bibliothek, passiert das nicht.

Ungültige Escapes und ihre Fehlermeldungen

Die häufigsten Fehler:

{ "a": "it\'s", "b": "\x41", "c": "\u12" }
  • \': Einfache Anführungszeichen musst du in JSON nie escapen. Schreibe "it's".
  • \x41: Gibt es nicht. Nimm \u0041 oder schreibe das Zeichen direkt.
  • \u12: Nach \u müssen genau vier Hex-Ziffern folgen.

Ein Parser meldet nur den ersten Fehler. Für die Zeile oben meldet Node.js (Chrome zeigt dieselbe V8-Meldung) den Fehler bei \'; steht "\u12" allein in einem Objekt wie { "c": "\u12" }, kommt die zweite Meldung:

Bad escaped character in JSON at position 11 (line 1 column 12)
Bad Unicode escape in JSON at position 12 (line 1 column 13)

Python meldet für dieselbe Zeile Invalid \escape: line 1 column 11 (char 10) und für "\u12" Invalid \uXXXX escape. Der genaue Wortlaut hängt von der Version ab, Firefox und Safari formulieren anders. Die Position zeigt in der Regel auf oder direkt hinter den Backslash.

Windows-Pfade

Ein Windows-Pfad ist der häufigste Fall von zu wenig escapten Backslashes:

{ "pfad": "C:\Users\ada" }

\U ist keine gültige Escape-Sequenz, der Parser meldet Bad escaped character. Bei manchen Pfaden geht es dagegen scheinbar gut und liefert Unsinn: "C:\temp" ist gültiges JSON, aber \t wird zum Tabulator. Jeder Backslash muss verdoppelt werden:

{ "pfad": "C:\\Users\\ada" }

Nach dem Parsen steht im Programm der einfache Pfad C:\Users\ada. Das Verdoppeln gilt nur für den JSON-Text. Alternative: Windows akzeptiert in den meisten Programmen auch Schrägstriche (C:/Users/ada), die du nicht escapen musst.

JSON.stringify macht das für dich

Der sicherste Weg: Setze JSON nie von Hand aus Strings zusammen, sondern erzeuge es aus Daten.

const daten = {
  pfad: "C:\\Users\\ada",
  zitat: 'Sie sagte "Hallo"',
  text: "Zeile 1\nZeile 2",
  emoji: "😀",
};

console.log(JSON.stringify(daten));
// {"pfad":"C:\\Users\\ada","zitat":"Sie sagte \"Hallo\"","text":"Zeile 1\nZeile 2","emoji":"😀"}

JSON.stringify() escaped ", \ und Steuerzeichen, lässt Umlaute und Emoji unverändert und schreibt einzelne, unpaarige Surrogate als \ud83d, damit das Ergebnis gültig bleibt. In Python ist das json.dumps(), auf der Kommandozeile hilft jq.

Vorsicht bei doppelter Anwendung: Ist ein Wert schon ein JSON-Text, wird er beim nochmaligen Serialisieren komplett neu escaped.

JSON.stringify(JSON.stringify({ a: 1 }));
// "{\"a\":1}"

Wenn in deinen Daten plötzlich überall \" und \\ stehen, wurde vermutlich zweimal serialisiert. Der Gegenschritt ist ein zweites JSON.parse().

Kurz gesagt

  • Pflicht: " als \", \ als \\ und Steuerzeichen (U+0000 bis U+001F) als \n, \t, \r, \b, \f oder \u00XX.
  • Erlaubt sind nur \" \\ \/ \b \f \n \r \t \uXXXX. \', \x41 und \0 sind ungültig.
  • Umlaute und Emoji darfst du direkt als UTF-8 schreiben; \u00e4 ist gleichwertig. Zeichen über U+FFFF werden als Surrogatpaar geschrieben, etwa \ud83d\ude00.
  • Windows-Pfade brauchen doppelte Backslashes: C:\\Users\\ada.
  • Erzeuge JSON mit JSON.stringify() oder json.dumps(), statt Strings von Hand zu bauen.

Weiterlesen: JSON-Fehler finden und beheben, Anführungszeichen in JSON und „Unexpected token“ in JSON. Zum Prüfen deines Textes: der JSON-Formatierer.