SyntaxError: Bad control character in string literal in JSON at position N significa que há um caractere de controle cru e sem escape —— uma tab, quebra de linha, retorno de carro, ou qualquer caractere no intervalo U+0000–U+001F —— dentro de uma string JSON. A especificação JSON proíbe eles. Este artigo explica por quê, de onde vêm, e como se livrar deles.
Qual erro de string eu estou tendo?
- Bad control character —— uma tab/quebra de linha/null byte cru está dentro de uma string sem escape.
- Bad escaped character —— uma
\é seguida por algo que JSON não permite (ex.\x, caminhos do Windows). - Unterminated string —— uma string foi aberta com
"mas nunca foi fechada.
O que é um caractere de controle?
Os primeiros 32 code points Unicode (U+0000 até U+001F) são caracteres de controle —— códigos de formatação invisíveis herdados das máquinas teletipo. Alguns familiares:
| Code point | Nome | Escape JSON |
|---|---|---|
U+0000 | Null | |
U+0008 | Backspace | \b |
U+0009 | Tab horizontal | \t |
U+000A | Line feed (quebra de linha) | \n |
U+000C | Form feed | \f |
U+000D | Carriage return | \r |
U+001B | Escape (sequências ANSI começam aqui) | |
A especificação JSON (RFC 8259 §7) é explícita: todos os caracteres nesse intervalo precisam ser escapados quando aparecem dentro de uma string JSON. Uma quebra de linha crua dentro de uma string com aspas é um erro de sintaxe, não um valor.
Por que quebras de linha cruas quebram o JSON
Considere um valor de string que contém um caractere real de quebra de linha:
// Como os bytes parecem (a quebra de linha é literal, não \n):
{"message":"line one
line two"}
JSON.parse('{"message":"line one\nline two"}')
// SyntaxError: Bad control character in string literal in JSON at position 20O parser vê a aspa dupla de abertura, lê caracteres, e então bate num byte 0x0A cru. Como uma string JSON precisa ser uma sequência única e ininterrupta entre duas aspas duplas na mesma linha lógica, a quebra de linha pelada termina a string inesperadamente.
A representação correta usa a sequência de escape de dois caracteres \n:
// Correto —— \n é o escape, não uma quebra de linha literal
{"message":"line one\nline two"}
JSON.parse('{"message":"line one\nline two"}') // ✓ funcionaComo caracteres de controle entram no JSON
1. Construindo strings JSON com template literals ou concatenação
const note = `first line
second line`; // quebra de linha real do template literal
const json = `{"note":"${note}"}`; // quebra de linha crua embutida na string
JSON.parse(json); // SyntaxError: Bad control character…
// ✓ Solução: sempre use JSON.stringify para valores, nunca interpole manualmente
const json = JSON.stringify({ note }); // escapa a quebra de linha automaticamente2. Lendo arquivos e embutindo conteúdo tal e qual
import fs from 'fs';
const content = fs.readFileSync('notes.txt', 'utf8');
// content pode conter \n, \r\n, \t, etc.
// ❌ Construção manual de string —— embute caracteres de controle crus
const json = '{"content":"' + content + '"}';
// ✓ JSON.stringify lida com todo o escape
const json = JSON.stringify({ content });3. Respostas de API de sistemas que não escapam a saída
Alguns sistemas back-end (scripts PHP legado, serializadores customizados, triggers de banco que constroem JSON manualmente) emitem valores de campo com quebras de linha ou tabs sem escape. Você recebe JSON sintaticamente inválido e o response.json() lança.
Diagnostique logando o tamanho cru do body e os primeiros 200 caracteres. Procure por quebras de linha visíveis dentro do que deveria ser um valor de string.
4. Copiar-colar de um terminal (sequências de escape ANSI)
A saída do terminal contém códigos de cor ANSI que começam com o caractere Escape (0x1B) seguido de [. Se você cola saída de terminal numa string JSON, cada reset de cor (\x1B[0m) vira um caractere de controle.
// Saída de terminal colada numa string JSON:
{"log":"\u001B[32mOK\u001B[0m request processed"}
// ^^^ byte ESC cru —— deveria ser a sequência de escape \u001B de 6 caracteres5. Null bytes de dados binários
Ler parte de um arquivo binário, uma coluna BLOB de banco, ou uma string estilo C dentro de um campo JSON pode embutir null bytes (U+0000), que são a fonte mais comum desse erro em pipelines de banco-para-JSON.
Como consertar
Prevenção: sempre use JSON.stringify
Essa é a solução definitiva. JSON.stringify() escapa corretamente cada caractere de controle —— você nunca precisa escapar manualmente.
// ✓ Seguro não importa o que userInput contenha
const json = JSON.stringify({ message: userInput });Reparo: tirar ou escapar depois do fato
Se você recebe JSON quebrado de uma fonte externa e não pode consertar o produtor, dá pra sanitizar a string crua antes de parsear. A abordagem mais segura é escapar os caracteres de controle pelados:
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);Se os caracteres de controle são ruído (códigos ANSI, null bytes) em vez de dado significativo, tirar é mais simples:
// Tira todos os caracteres de controle exceto \t, \n, \r (comuns em texto)
const cleaned = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
const data = JSON.parse(cleaned);Mais duas fontes do mundo real
- BOM UTF-8 na posição 0. Arquivos salvos como «UTF-8 com BOM» começam com
0xEF 0xBB 0xBF. RFC 8259 proíbe BOM em JSON, e oJSON.parserejeita com um erro em posição 0 fácil de confundir com caractere de controle. Tire comtext.replace(/^/, '')antes de parsear, ou salve o arquivo como «UTF-8» (sem BOM) no seu editor. - VS Code: «Remove Control Characters». Se você está limpando um arquivo único na mão, o
Selection → Remove All Occurrences of Find Matchdo VS Code combinado com find regex em[\x00-\x08\x0b\x0c\x0e-\x1f](preservando os reais\t\n\r) deleta bytes de controle invisíveis numa passada. Existe também um comando «Remove Control Characters» em algumas extensões se você faz isso com frequência.
Detectando a posição ofensora
A mensagem de erro inclui uma posição em bytes. Use ela para inspecionar o que tem ali:
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-escapa os caracteres de controle pra você poder ver
// ex. "...line one\nline two..." —— aquele \n era uma quebra de linha crua
}
}Perguntas frequentes
O que é um bad control character em JSON?
Um caractere cru e sem escape no intervalo U+0000–U+001F —— uma tab, quebra de linha, retorno de carro, null byte, ou escape ANSI —— dentro de uma string JSON. RFC 8259 §7 exige que todo caractere desses esteja escapado (por exemplo \n para uma quebra de linha).
Como consertar um erro bad control character?
O conserto definitivo é construir o JSON com JSON.stringify(), que escapa caracteres de controle automaticamente. Se você recebe JSON quebrado que não dá pra consertar na fonte, escape ou tire os caracteres de controle com uma regex antes de parsear (veja os snippets acima).
Por que uma quebra de linha crua quebra o JSON?
Uma string JSON precisa ser uma sequência ininterrupta entre duas aspas duplas. Um byte literal de quebra de linha (0x0A) termina a string cedo demais, então o parser reporta um erro de caractere de controle. A representação válida é o escape de dois caracteres \n.
Como tiro códigos de cor ANSI do JSON?
Sequências ANSI começam com o byte Escape (U+001B). Tire elas com raw.replace(/\[[0-9;]*m/g, '') antes de parsear, ou evite colar saída crua de terminal em valores de string JSON em primeiro lugar.
Repare JSON quebrado no seu navegador
Cole seu JSON quebrado no JSON Fix no fixjson.org. O parser tolerante identifica violações de caractere de controle e reporta a posição exata. Para casos simples ele pode reparar a string automaticamente.
- JSON Fix —— valide, repare e formate JSON inválido
- Consertar «[object Object] is not valid JSON» —— o guia completo de erros de sintaxe JSON
- Como consertar erros «Unexpected Token» do JSON.parse —— cada variante do SyntaxError e sua causa
- Unexpected end of JSON input —— quando a estrutura é cortada antes de fechar
- Unexpected token o explicado —— quando você passa um objeto em vez de uma string pro JSON.parse