Zum Inhalt springen
OneKitly
in einem Stringwert das Element zu früh schließen und den Rest deiner Daten dem HTML-Parser als Markup übergeben würde. Die Folge mit einem Backslash zu brechen verhindert das. Überall sonst — eine API-Antwort, eine Datei auf der Platte, eine Nachricht in einer Queue — bläht das Escapen von Schrägstrichen nur die Nutzlast auf. Das Werkzeug liefert die Option ausgeschaltet aus, was die richtige Voreinstellung ist, und sie einzuschalten ändert nichts an der Bedeutung des Werts nach dem Parsen."}},{"@type":"Question","name":"Das Werkzeug hat meinen String angenommen, mein Parser lehnt ihn ab. Warum?","acceptedAnswer":{"@type":"Answer","text":"Weil die Entschärfungsrichtung absichtlich nachsichtiger ist als ein Dokumentparser, und der Unterschied sind Steuerzeichen. Ein JSON-Parser lehnt ein rohes Steuerzeichen ab, das wörtlich in einem String steht — die Meldung lautet meist „bad control character in string literal“ — während dieses Werkzeug es annimmt und zurückgibt, in der Annahme, dass du ein Fragment untersuchst und kein Dokument prüfst. Beschwert sich dein Parser und das Werkzeug nicht, suche nach einem rohen Tabulator oder Zeilenumbruch, der als Escape hätte geschrieben werden müssen. Das Werkzeug lehnt weiterhin die drei Mängel ab, die ein Fragment unlesbar machen: eine unbekannte Escape-Sequenz, ein unvollständiges oder nicht hexadezimales \\u und einen am Ende hängenden Backslash — und nennt dir jeweils die Position."}}]},{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"OneKitly","item":"https://www.onekitly.com/de"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://www.onekitly.com/de/blog"},{"@type":"ListItem","position":3,"name":"Einen String für JSON escapen: drei Zeichen sind Pflicht, und eines ist eine Falle","item":"https://www.onekitly.com/de/blog/einen-string-fuer-json-escapen"}]}]

Einen String für JSON escapen: drei Zeichen sind Pflicht, und eines ist eine Falle

Veröffentlicht am 31.7.2026 · 11 Min. Lesezeit · Entwickler-Tools

Daniel Okonkwo

Daniel OkonkwoFront-end-Entwickler und Tech-Redakteur bei OneKitly

Web-Performance · Dateiformate

Anhand von 4 Quellen geprüft

Profil ansehen
Kurz gesagt

RFC 8259 Abschnitt 7 nennt drei Dinge, die in einem JSON-String escaped werden MÜSSEN, und sonst nichts: das Anführungszeichen, den Backslash und jedes Steuerzeichen von U+0000 bis U+001F. Alles andere darf als wörtliches UTF-8 stehen bleiben — Akzentbuchstaben, Ideogramme, Emoji, das DEL-Zeichen bei U+007F, der Zeilentrenner bei U+2028. Jedes Zeichen DARF escaped werden, und daher stammt die Angewohnheit, \/ für einen Schrägstrich zu schreiben: JSON verlangt es nie, und der einzige Grund dafür ist JSON in einem HTML-Script-Tag, wo die Folge </ aufgebrochen werden muss. Es gibt acht Zwei-Zeichen-Escapes — \" \\ \/ \b \f \n \r \t — und alles Übrige nutzt \uXXXX, vier Hexziffern, eine UTF-16-Einheit. Ein Zeichen außerhalb der Basic Multilingual Plane braucht zwei davon: Ein Emoji wird als zwölfzeichiges Surrogatpaar geschrieben, nie als sechsstelliges Escape. Genau dort sitzt die Falle. JSON-Strings sind Folgen von UTF-16-Einheiten, und die Grammatik erlaubt eine Einheit, die eine Hälfte ohne Partner ist — die Spezifikation sagt das in Abschnitt 8.2 und nennt � als Beispiel. So ein JSON-Text lässt sich klaglos parsen. Aber Abschnitt 8.1 verlangt für zwischen Systemen ausgetauschtes JSON UTF-8, und RFC 3629 Abschnitt 3 verbietet UTF-8, irgendetwas zwischen U+D800 und U+DFFF zu kodieren. An diesem Werkzeug gemessen: Der Text {"k":"id-�-end"} parst, und die UTF-8-Kodierung des Ergebnisses liefert die Bytes 69 64 2d ef bf bd 2d 65 6e 64 — die einsame Hälfte ist zu U+FFFD geworden, dem Ersatzzeichen, und der Wert kommt nicht mehr unversehrt zurück. Nichts hat eine Ausnahme geworfen. Das ist der Fehler, nach dem man suchen muss.

RFC 8259 verlangt genau drei Dinge, die in einem JSON-String escaped werden müssen. Alles andere ist optional. Was Pipelines wirklich zerlegt, ist ein einzelnes Surrogat: in JSON-Text legal, in UTF-8 unmöglich und stillschweigend ersetzt, sobald deine Daten geschrieben werden.

Drei Pflicht, alles andere optional

Die Regel ist kürzer, als die meisten annehmen. Alle Unicode-Zeichen dürfen zwischen den Anführungszeichen stehen außer denen, die escaped werden müssen: das Anführungszeichen selbst, der Backslash und die Steuerzeichen U+0000 bis U+001F. Drei Punkte. Ein Windows-Pfad voller Backslashes und Anführungszeichen, C:\Users\Léa\"Bericht".txt, braucht jeden Backslash verdoppelt und jedes Anführungszeichen escaped, und das é bleibt genau so stehen. Es gibt keine Regel zu Akzenten, keine zu nichtlateinischen Schriften, keine zu Emoji.

Zwei Grenzen sollte man prüfen statt raten. Das DEL-Zeichen bei U+007F ist hierfür kein Steuerzeichen — der Bereich endet bei U+001F — es bleibt also wörtlich, und der Durchlauf durch das Werkzeug bestätigt das: Die Ausgabe ist das Zeichen selbst, ein Byte in UTF-8, kein Escape. Der Zeilentrenner U+2028 und der Absatztrenner U+2029 bleiben ebenso unangetastet, denn auch sie liegen nicht im Bereich U+0000 bis U+001F. Das war einst eine echte Gefahr, wenn JSON in ein Inline-Skript eingefügt wurde, weil ein JavaScript-String-Literal sie nicht enthalten durfte; die Sprache wurde 2019 geändert, um es zu erlauben. Muss deine Ausgabe noch einen alten Parser überstehen, escaped der Nur-ASCII-Modus des Werkzeugs sie zu \u2028 und \u2029, zusammen mit allem oberhalb des ASCII-Bereichs.

Der escapte Schrägstrich verdient einen Absatz, denn er ist das Häufigste, was Leute tun, ohne zu wissen warum. JSON erlaubt \/ und verlangt es nie. Die Gewohnheit stammt daher, JSON in ein HTML-Script-Element zu setzen, wo ein wörtliches </script> in einem String das Element zu früh schließen und den Rest deiner Daten dem HTML-Parser überlassen würde; das Escapen des Schrägstrichs zerlegt die Folge, und der Browser sieht sie nie. Das Werkzeug hat dafür einen Schalter, standardmäßig aus, und das ist die richtige Voreinstellung: Schalte ihn an, wenn du einbettest, und lass ihn überall sonst aus. Schrägstriche in einer API-Antwort zu escapen macht die Nutzlast größer und die Werte kein bisschen anders.

UTF-16-Einheiten, keine Zeichen

Das \uXXXX-Escape trägt genau vier Hexziffern, also eine UTF-16-Einheit, und deckt U+0000 bis U+FFFF ab. Alles darüber muss mit zweien geschrieben werden, und die Spezifikation ist deutlich: Um ein erweitertes Zeichen außerhalb der Basic Multilingual Plane zu escapen, wird es als zwölfzeichige Folge dargestellt, die das UTF-16-Surrogatpaar kodiert. Eine sechsstellige Form gibt es nicht. Schick ein grinsendes Gesicht im ASCII-Modus durch das Werkzeug, und es gibt 😀 aus, was richtig ist; falls du irgendwo \u1f600 in jemandes Ausgabe siehst, ist das kein JSON, sondern eine andere Escape-Konvention, die aus Python oder einer Shell hereingeraten ist.

Deshalb zeigt das Werkzeug vier verschiedene Zählungen für denselben String, und deshalb widersprechen sie einander. Ein grinsendes Gesicht ist ein Codepunkt, zwei UTF-16-Einheiten und vier UTF-8-Bytes. Ein Familien-Emoji aus vier Figuren, verbunden durch Zero-Width-Joiner, sind sieben Codepunkte, elf UTF-16-Einheiten und fünfundzwanzig UTF-8-Bytes. Ist deine Datenbankspalte mit zwanzig Zeichen deklariert, so ist es eine Eigenschaft der Datenbank und nicht deiner Daten, welche dieser drei Zahlen gemeint ist, und im Abstand dazwischen wohnen die Abschneidefehler.

Das einsame Surrogat und wie es in deine Daten kommt

Ein Surrogatpaar sind zwei Codeeinheiten, die nur zusammen etwas bedeuten: eine hohe Hälfte von U+D800 bis U+DBFF, gefolgt von einer niedrigen von U+DC00 bis U+DFFF. Die JSON-Grammatik erzwingt die Paarung nicht. Abschnitt 8.2 von RFC 8259 sagt das wörtlich und merkt an, dass die Spezifikation Stringwerten erlaubt, Bitfolgen zu enthalten, die keine Unicode-Zeichen kodieren können, und nennt � als Beispiel. Somit ist {"k":"id-�-end"} ein syntaktisch gültiger JSON-Text. Jeder Parser, dem du begegnen wirst, akzeptiert ihn.

Der Widerspruch kommt einen Abschnitt früher. Abschnitt 8.1 verlangt, dass JSON-Text, der zwischen Systemen außerhalb eines geschlossenen Ökosystems ausgetauscht wird, in UTF-8 kodiert ist, und RFC 3629, die UTF-8 definiert, verbietet die Kodierung von Zeichennummern zwischen U+D800 und U+DFFF, eben weil sie für UTF-16 reserviert sind. Ein Wert, den JSON erlaubt, lässt sich also in der Kodierung, die JSON vorschreibt, nicht ausdrücken. Was Implementierungen statt eines Fehlers tun, ist ersetzen: Schick diesen JSON-Text durch einen Parser und kodiere das Ergebnis neu, und die Bytes kommen als 69 64 2d ef bf bd 2d 65 6e 64 zurück. Diese drei mittleren Bytes, EF BF BD, sind U+FFFD, das Ersatzzeichen. Der Wert, der hineinging, ist nicht der Wert, der herauskam, und nirgends wurde ein Fehler gemeldet.

Niemand tippt ein einsames Surrogat. Sie entstehen durch Abschneiden. Nimm den String Rapport 📊 final, sechzehn UTF-16-Einheiten für fünfzehn Codepunkte, und kürze ihn auf neun Einheiten, damit er in ein Label passt — heraus kommt Rapport gefolgt von einem halben Emoji, und das Werkzeug escaped es als Rapport �. Das ist der alltägliche Ursprung: eine Datenbankspalte mit Zeichengrenze, eine Oberfläche, die einen Titel kürzt, eine Logzeile auf feste Breite geschnitten, ein Import, der Felder fester Länge kopiert. Überall dort, wo ein String durch Zählen von Einheiten statt Codepunkten geschnitten wird, kann der Schnitt mitten in ein Paar fallen.

Das Werkzeug bietet zwei Wege, eines zu erkennen, bevor es auf Reisen geht. Der Escaper schreibt ein einsames Surrogat immer als \uXXXX, selbst in seinem nicht-ASCII-Standardmodus — genau wie ein modernes JSON.stringify — ein unerwartetes � oder � in der Ausgabe ist also das Signal. Und der Bytezähler, der dieselben Zahlen liefert wie ein echter UTF-8-Kodierer, hat diese Hälfte bereits mit drei Bytes veranschlagt: Drei Bytes kostet U+FFFD, ein String, dessen escapte Form ein einsames Surrogat enthält, wird also schon für ein Ersatzzeichen berechnet, bevor er deinen Bildschirm verlassen hat.

Wo dieses Werkzeug absichtlich nachsichtiger ist als ein Parser

In Richtung Escapen entspricht die Standardausgabe des Werkzeugs Zeichen für Zeichen dem, was ein modernes JSON.stringify erzeugt, über alle geprüften Fälle hinweg: Steuerzeichen, Emoji, kombinierende Akzente, Windows-Pfade, einsame Surrogate. Wer nur die Standardeinstellungen nutzt, erhält den Inhalt eines JSON-Strings ohne die äußeren Anführungszeichen, und nichts Überraschenderes als das.

In Richtung Entschärfen ist es absichtlich lockerer, und es lohnt sich zu wissen, wo. Ein echter JSON-Parser lehnt ein rohes Steuerzeichen in einem String ab: Füge ein wörtliches Klingelzeichen in ein JSON-Dokument ein, und du bekommst einen Syntaxfehler, der genau das sagt. Dieses Werkzeug nimmt es an und gibt es zurück, mit der Begründung, dass du ein Fragment untersuchst und kein Dokument prüfst. Dass ein String den Entschärfungsmodus klaglos passiert, beweist also nicht, dass das umgebende JSON gültig ist. Es lehnt weiterhin die drei Dinge ab, die ein Fragment sinnlos machen: ein unbekanntes Escape wie \x, ein unvollständiges oder nicht hexadezimales \u und einen Backslash, der ganz am Ende hängt — und nennt jedes Mal Position und beanstandete Folge.

Der Nur-ASCII-Schalter ist die andere Einstellung, die man verstehen sollte, denn sie ändert die Größe deiner Nutzlast, nicht ihre Bedeutung. Alles oberhalb des ASCII-Bereichs zu escapen macht die Ausgabe durch ein System mit unsicherer Kodierung transportfähig, zu einem echten Preis: Café wächst von vier Zeichen auf neun, ein einzelnes Emoji von zwei auf zwölf. Nutze ihn, wenn der Transportweg fragwürdig ist — ein alter Log-Aggregator, eine URL, ein Header, eine Datenbank, deren Sortierung du nicht kennst — und sonst nicht, denn UTF-8 ist das, was Abschnitt 8.1 verlangt, und es ist kleiner.

Was escaped werden muss, was darf, und was das Werkzeug damit tatsächlich macht
EingabeVon RFC 8259 gefordert?Ausgabe des Werkzeugs (Standardmodus)
Ein AnführungszeichenJa — PflichtZwei-Zeichen-Escape
Ein BackslashJa — PflichtVerdoppelt
Ein Tab, U+0009Ja — Pflicht (Steuerzeichen)Die Kurzform für den Tab, kein sechszeichiges Escape
Das Klingelzeichen, U+0007Ja — Pflicht (Steuerzeichen ohne Kurzform)Ein sechszeichiges Escape, endend auf 0007
DEL, U+007FNein — der Steuerbereich endet bei U+001FWörtlich durchgereicht, ein UTF-8-Byte
Ein SchrägstrichNein — darf escaped werden, muss nieBleibt stehen, außer die Schrägstrich-Option ist an
Ein Emoji außerhalb der BMPNein — wörtliches UTF-8 genügtBleibt unverändert; im ASCII-Modus ein zwölfzeichiges Surrogatpaar
Eine einsame SurrogathälfteDarstellbar, aber nicht in UTF-8 kodierbarImmer als \uXXXX escaped, auch im Standardmodus — das Warnzeichen
JSON-String escapen / un-escapenWandle Rohtext in einen JSON-sicheren String und zurück — Steuerzeichen, \uXXXX, Emoji-Surrogatpaare und einzelne Surrogate wie bei JSON.stringify.Tool ausprobieren

Häufige Fragen

Welche Zeichen muss ich in einem JSON-String escapen?
Genau drei Sorten, laut RFC 8259 Abschnitt 7: das Anführungszeichen, der Backslash und jedes Steuerzeichen von U+0000 bis U+001F. Sonst ist nichts Pflicht. Akzentbuchstaben, Ideogramme, Emoji, DEL bei U+007F und die Trenner U+2028 und U+2029 dürfen alle als wörtliches UTF-8 im String stehen. Von den Steuerzeichen haben fünf Kurzformen — Rückschritt, Seitenvorschub, Zeilenvorschub, Wagenrücklauf und Tabulator — und der Rest braucht die sechszeichige \u-Form, so schreibt man ein Klingelzeichen oder ein Nullbyte. Alles darf escaped werden, wenn man will, und deshalb kommt völlig gültiges JSON manchmal mit jedem Nicht-ASCII-Zeichen ausgeschrieben an: Es ist größer, nicht korrekter.
Wie schreibe ich ein Emoji in einen JSON-String?
Zwei Wege, und beide sind richtig. Lass es als wörtliches UTF-8 stehen — nichts in der Spezifikation verlangt ein Escape, und genau das tut das Werkzeug standardmäßig. Oder schreibe, wenn die Nutzlast ASCII sein muss, das Surrogatpaar: zwei \uXXXX-Escapes, zwölf Zeichen insgesamt, eines für die hohe und eines für die niedrige Hälfte. Eine sechsstellige Form gibt es nicht: \u1f600 ist kein JSON, und ein Parser, der es annimmt, tut nicht, was der Standard sagt. Der Preis der ASCII-Form ist real — ein Emoji wächst von zwei Zeichen auf zwölf — nimm sie also nur, wenn dem Transportweg kein UTF-8 anvertraut werden kann.
Was ist ein einsames Surrogat und warum geht davon etwas kaputt?
Es ist die Hälfte einer Zwei-Einheiten-Kodierung ohne Partner: eine Codeeinheit zwischen U+D800 und U+DFFF, die für sich steht. JSON erlaubt sie — RFC 8259 Abschnitt 8.2 räumt ausdrücklich ein, dass Stringwerte Bitfolgen enthalten dürfen, die keine Unicode-Zeichen kodieren können, und nennt ein ungepaartes Surrogat als Beispiel — aber UTF-8 kann sie nicht ausdrücken, weil RFC 3629 die Kodierung von allem in diesem Bereich verbietet. Da Abschnitt 8.1 für den Austausch UTF-8 verlangt, stoßen die beiden Regeln zusammen, und in der Praxis wird ersetzt: Die Hälfte wird zu U+FFFD, dem Ersatzzeichen, drei Bytes, und der ursprüngliche Wert ist fort. Unterwegs wird nirgends eine Ausnahme geworfen, weshalb das erst Tage später auftaucht — als Suche, die nichts mehr findet, oder als Schlüssel, der nicht mehr passt.
Soll ich den Schrägstrich escapen?
Nur wenn du das JSON in ein HTML-Script-Element einbettest. JSON erlaubt \/ und verlangt es nie; das Escape existiert, weil ein wörtliches </script> in einem Stringwert das Element zu früh schließen und den Rest deiner Daten dem HTML-Parser als Markup übergeben würde. Die Folge mit einem Backslash zu brechen verhindert das. Überall sonst — eine API-Antwort, eine Datei auf der Platte, eine Nachricht in einer Queue — bläht das Escapen von Schrägstrichen nur die Nutzlast auf. Das Werkzeug liefert die Option ausgeschaltet aus, was die richtige Voreinstellung ist, und sie einzuschalten ändert nichts an der Bedeutung des Werts nach dem Parsen.
Das Werkzeug hat meinen String angenommen, mein Parser lehnt ihn ab. Warum?
Weil die Entschärfungsrichtung absichtlich nachsichtiger ist als ein Dokumentparser, und der Unterschied sind Steuerzeichen. Ein JSON-Parser lehnt ein rohes Steuerzeichen ab, das wörtlich in einem String steht — die Meldung lautet meist „bad control character in string literal“ — während dieses Werkzeug es annimmt und zurückgibt, in der Annahme, dass du ein Fragment untersuchst und kein Dokument prüfst. Beschwert sich dein Parser und das Werkzeug nicht, suche nach einem rohen Tabulator oder Zeilenumbruch, der als Escape hätte geschrieben werden müssen. Das Werkzeug lehnt weiterhin die drei Mängel ab, die ein Fragment unlesbar machen: eine unbekannte Escape-Sequenz, ein unvollständiges oder nicht hexadezimales \u und einen am Ende hängenden Backslash — und nennt dir jeweils die Position.

Artikel, die dich interessieren könnten

Alle Ratgeber

Ähnliche Tools

Das hier beschreibt das Verhalten eines Dateiformats und eines Renderers, geprüft an der genannten Spezifikation und am Code des Werkzeugs, wie er heute aussieht. Renderer sind sich uneinig: GitHub, GitLab, ein Static-Site-Generator und die Vorschau deines Editors sind vier verschiedene Implementierungen, und was in der einen läuft, kann in der anderen scheitern. Nichts davon ist eine Zusage über deine Veröffentlichungskette — teste das Ergebnis dort, wo es wirklich erscheinen wird, und behandle jedes Werkzeug, dieses eingeschlossen, als etwas zum Nachprüfen und nicht zum Glauben.

Quellen

Hast du einen Fehler in diesem Artikel entdeckt?