SyntaxError: Bad control character in string literal in JSON at position N bedeutet, dass ein rohes, nicht escaptes Steuerzeichen —— ein Tab, Zeilenumbruch, Carriage Return, oder irgendein Zeichen im Bereich U+0000–U+001F —— innerhalb eines JSON-Strings sitzt. Die JSON-Spezifikation verbietet sie. Dieser Artikel erklärt warum, woher sie kommen, und wie du sie loswirst.
Welcher String-Fehler trifft mich?
- Bad control character —— ein rohes Tab/Newline/Null-Byte sitzt innerhalb eines Strings ohne Escaping.
- Bad escaped character —— ein
\wird gefolgt von etwas, das JSON nicht erlaubt (z. B.\x, Windows-Pfade). - Unterminated string —— ein String wurde mit
"geöffnet, aber nie geschlossen.
Was ist ein Steuerzeichen?
Die ersten 32 Unicode-Codepoints (U+0000 bis U+001F) sind Steuerzeichen —— unsichtbare Formatierungscodes, geerbt von Fernschreibern. Einige bekannte:
| Codepoint | Name | JSON-Escape |
|---|---|---|
U+0000 | Null | |
U+0008 | Backspace | \b |
U+0009 | Horizontaler Tab | \t |
U+000A | Line Feed (Zeilenumbruch) | \n |
U+000C | Form Feed | \f |
U+000D | Carriage Return | \r |
U+001B | Escape (ANSI-Sequenzen beginnen hier) | |
Die JSON-Spezifikation (RFC 8259 §7) ist explizit: alle Zeichen in diesem Bereich müssen escapt werden, wenn sie innerhalb eines JSON-Strings vorkommen. Ein roher Zeilenumbruch in einem gequoteten String ist ein Syntaxfehler, kein Wert.
Warum rohe Zeilenumbrüche JSON brechen
Stell dir einen String-Wert vor, der ein echtes Zeilenumbruch-Zeichen enthält:
// Wie die Bytes aussehen (der Zeilenumbruch ist literal, nicht \n):
{"message":"line one
line two"}
JSON.parse('{"message":"line one\nline two"}')
// SyntaxError: Bad control character in string literal in JSON at position 20Der Parser sieht das öffnende doppelte Anführungszeichen, liest Zeichen, dann trifft er auf ein rohes 0x0A-Byte. Da ein JSON-String eine einzelne, ununterbrochene Folge zwischen zwei doppelten Anführungszeichen auf derselben logischen Zeile sein muss, beendet der nackte Zeilenumbruch den String unerwartet.
Die korrekte Darstellung nutzt die Zwei-Zeichen-Escape-Sequenz \n:
// Korrekt —— \n ist der Escape, kein literaler Zeilenumbruch
{"message":"line one\nline two"}
JSON.parse('{"message":"line one\nline two"}') // ✓ funktioniertWie Steuerzeichen ins JSON gelangen
1. JSON-Strings mit Template Literals oder Konkatenation bauen
const note = `first line
second line`; // echter Zeilenumbruch aus dem Template Literal
const json = `{"note":"${note}"}`; // roher Zeilenumbruch im String eingebettet
JSON.parse(json); // SyntaxError: Bad control character…
// ✓ Fix: nutze immer JSON.stringify für Werte, niemals manuell interpolieren
const json = JSON.stringify({ note }); // escapt den Zeilenumbruch automatisch2. Dateien lesen und Inhalt wortwörtlich einbetten
import fs from 'fs';
const content = fs.readFileSync('notes.txt', 'utf8');
// content kann \n, \r\n, \t etc. enthalten
// ❌ Manuelles String-Bauen —— bettet rohe Steuerzeichen ein
const json = '{"content":"' + content + '"}';
// ✓ JSON.stringify behandelt jegliches Escaping
const json = JSON.stringify({ content });3. API-Antworten von Systemen, die Output nicht escapen
Manche Backend-Systeme (Legacy-PHP-Skripte, eigene Serialisierer, Datenbank-Trigger, die JSON manuell bauen) liefern Feldwerte mit unescapten Zeilenumbrüchen oder Tabs aus. Du bekommst syntaktisch ungültiges JSON, und response.json() wirft.
Diagnostiziere, indem du die rohe Body-Länge und die ersten 200 Zeichen loggst. Achte auf sichtbare Zeilenumbrüche in dem, was eigentlich ein String-Wert sein sollte.
4. Aus einem Terminal kopieren (ANSI-Escape-Sequenzen)
Terminal-Output enthält ANSI-Farbcodes, die mit dem Escape-Zeichen (0x1B) beginnen, gefolgt von [. Wenn du Terminal-Output in einen JSON-String einfügst, wird jeder Farb-Reset (\x1B[0m) zu einem Steuerzeichen.
// In einen JSON-String eingefügter Terminal-Output:
{"log":"\u001B[32mOK\u001B[0m request processed"}
// ^^^ rohes ESC-Byte —— sollte die 6-Zeichen-Sequenz \u001B sein5. Null-Bytes aus Binärdaten
Wenn du Teile einer Binärdatei, eine Datenbank-BLOB-Spalte oder einen C-artigen String in ein JSON-Feld einliest, kannst du Null-Bytes (U+0000) einbetten —— die häufigste Quelle dieses Fehlers in DB-zu-JSON-Pipelines.
Wie du es behebst
Prävention: nutze immer JSON.stringify
Das ist die endgültige Lösung. JSON.stringify() escapt jedes Steuerzeichen korrekt —— du musst nie manuell escapen.
// ✓ Sicher, egal was userInput enthält
const json = JSON.stringify({ message: userInput });Reparatur: nachträglich strippen oder escapen
Wenn du kaputtes JSON aus einer externen Quelle bekommst und den Producer nicht reparieren kannst, kannst du den rohen String vor dem Parsen säubern. Der sicherste Ansatz ist, nackte Steuerzeichen zu escapen:
function escapeControlChars(raw) {
return raw.replace(
/[\u0000-\u001F]/g,
(ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')
);
}
const fixed = escapeControlChars(rawFromApi);
const data = JSON.parse(fixed);Wenn die Steuerzeichen Rauschen sind (ANSI-Codes, Null-Bytes) statt sinnvolle Daten, ist Strippen einfacher:
// Strippe alle Steuerzeichen außer \t, \n, \r (die in Text üblich sind)
const cleaned = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
const data = JSON.parse(cleaned);Zwei weitere reale Quellen
- UTF-8-BOM an Position 0. Als „UTF-8 mit BOM" gespeicherte Dateien beginnen mit
0xEF 0xBB 0xBF. RFC 8259 verbietet ein BOM in JSON, undJSON.parselehnt es mit einem Position-0-Fehler ab, der leicht als Steuerzeichen-Fehler missgelesen wird. Strippe vor dem Parsen mittext.replace(/^/, ''), oder speichere die Datei als „UTF-8" (ohne BOM) in deinem Editor. - VS Code: „Remove Control Characters". Wenn du eine einzelne Datei manuell aufräumst, löscht VS Codes
Selection → Remove All Occurrences of Find Matchkombiniert mit Regex-Suche nach[\x00-\x08\x0b\x0c\x0e-\x1f](echte\t\n\rbleiben erhalten) unsichtbare Steuerbytes in einem Durchgang. Es gibt auch in manchen Erweiterungen einen „Remove Control Characters"-Befehl, wenn du das oft tust.
Die Fehlerstelle erkennen
Die Fehlermeldung enthält eine Byte-Position. Nutze sie, um zu prüfen, was dort steht:
try {
JSON.parse(raw);
} catch (e) {
const pos = Number(e.message.match(/position (\d+)/)?.[1]);
if (!isNaN(pos)) {
const context = raw.slice(Math.max(0, pos - 20), pos + 20);
console.log('Context around error:', JSON.stringify(context));
// JSON.stringify re-escapt die Steuerzeichen, damit du sie sehen kannst
// z. B. "...line one\nline two..." —— das \n war ein roher Zeilenumbruch
}
}Häufig gestellte Fragen
Was ist ein bad control character in JSON?
Ein rohes, nicht escaptes Zeichen im Bereich U+0000–U+001F —— ein Tab, Zeilenumbruch, Carriage Return, Null-Byte oder ANSI-Escape —— innerhalb eines JSON-Strings. RFC 8259 §7 verlangt, dass jedes solche Zeichen escapt wird (z. B. \n für einen Zeilenumbruch).
Wie behebe ich einen bad-control-character-Fehler?
Der endgültige Fix ist, das JSON mit JSON.stringify() zu bauen, das Steuerzeichen automatisch escapt. Wenn du kaputtes JSON empfängst, das du an der Quelle nicht reparieren kannst, escape oder strippe die Steuerzeichen mit einer Regex vor dem Parsen (siehe Snippets oben).
Warum bricht ein roher Zeilenumbruch JSON?
Ein JSON-String muss eine ununterbrochene Folge zwischen zwei doppelten Anführungszeichen sein. Ein literales Newline-Byte (0x0A) beendet den String vorzeitig, also meldet der Parser einen Steuerzeichen-Fehler. Die gültige Darstellung ist der Zwei-Zeichen-Escape \n.
Wie entferne ich ANSI-Farbcodes aus JSON?
ANSI-Sequenzen beginnen mit dem Escape-Byte (U+001B). Strippe sie vor dem Parsen mit raw.replace(/\[[0-9;]*m/g, ''), oder vermeide es von vornherein, rohen Terminal-Output in JSON-String-Werte zu kleben.
Kaputtes JSON im Browser reparieren
Füge dein kaputtes JSON in JSON Fix auf fixjson.org ein. Der nachsichtige Parser identifiziert Steuerzeichen-Verstöße und meldet die exakte Position. Für einfache Fälle kann er den String automatisch reparieren.
- JSON Fix —— ungültiges JSON validieren, reparieren und formatieren
- „[object Object] is not valid JSON" beheben —— der komplette Guide zu JSON-Syntaxfehlern
- Wie man JSON.parse-„Unexpected Token"-Fehler behebt —— jede Variante des SyntaxError und ihre Ursache
- Unexpected end of JSON input —— wenn die Struktur vor dem Schließen abgeschnitten ist
- Unexpected token o erklärt —— wenn du JSON.parse ein Objekt statt eines Strings übergibst