Pular para o conteúdo principal

Escape de JSON

O escape do JSON reescreve caracteres que, de outro modo, encerrariam ou corromperiam uma string. O RFC 8259 torna dois escapes obrigatórios — " e \ — e depois acrescenta cinco escapes de controle nomeados, um escape opcional de barra e a forma universal \uXXXX. Este guia cobre todos os escapes, os surrogate pairs por trás dos emojis e quando o escape ajuda ou atrapalha.

Só local

Escape de JSON: explicação

Toda string do JSON vive entre duas aspas duplas, então o formato precisa de um jeito de colocar uma aspa dupla dentro de uma string sem terminá-la — e um jeito de colocar uma barra invertida dentro de uma string sem iniciar um escape que não era intencional. A resposta é o escape: uma barra invertida colocada antes de um caractere muda o significado dele para o parser, transformando uma marca estrutural em dado comum.

O esquema é pequeno e fechado. O RFC 8259 define exatamente nove escapes de dois caracteres mais uma forma extensível, \uXXXX, e nada mais: uma barra invertida seguida de qualquer outro caractere é um erro de sintaxe, não uma passagem tolerante. Esse fechamento é uma vantagem — bugs de escape são sempre reproduzíveis, porque um parser em conformidade ou reconhece a sequência ou rejeita o documento.

Este guia percorre a tabela completa de escapes e depois as duas partes que as pessoas acham mais difíceis: \uXXXX e os surrogate pairs de que ele precisa para emojis, e o compromisso de tamanho de escapar texto não ASCII. Todos os exemplos podem ser copiados, e as ferramentas linkadas rodam no seu navegador, então você pode fazer o round-trip das suas próprias strings localmente, sem instalar nada.

O jeito mais rápido de ver o escape funcionando é observar um parser resolvê-lo — o Formatador de JSON que renderiza o resultado sem escapes ao lado da sua entrada enquanto reindenta um documento.

O escape é um capítulo da gramática; a cola de sintaxe JSON comprime o resto das regras — estrutura, espaço em branco, números — em uma única página de referência.

Os dois escapes que você nunca pode pular

Dentro de uma string, uma aspa dupla literal precisa ser escrita como " e uma barra invertida literal como \\. Esses dois são obrigatórios para qualquer produtor, porque sem eles um parser não consegue saber onde uma string termina nem onde um escape começa — não existe grafia alternativa. Todo outro escape da gramática é opcional no sentido de que o caractere dele poderia ter sido escrito literalmente; esses dois não têm nenhuma forma literal dentro de uma string.

A barra normal é o caso especial: \/ é legal e significa /, mas a barra literal nunca precisa de escape. A abreviação existe por um único motivo histórico — embutir JSON dentro de blocos <script> de HTML, onde a sequência </script> dentro de uma string poderia fechar cedo demais a tag ao redor. Alguns geradores ainda escapam toda barra por precaução; o custo é um documento um pouco maior e um pouco mais ruidoso.

Escapes obrigatórios e opcionais em um documento

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

O que um parser entrega

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

Os cinco caracteres de controle nomeados

Um texto pode conter legalmente caracteres de controle abaixo de U+0020 — tabulação, nova linha, retorno de carro e companhia —, mas um caractere de controle cru dentro de uma string do JSON não é interoperável: ele quebra ferramentas baseadas em linhas e derruba alguns parsers de vez. A norma, por isso, exige que caracteres de controle sejam escapados, e nomeia cinco deles: \b backspace (U+0008), \f form feed (U+000C), \n avanço de linha (U+000A), \r retorno de carro (U+000D) e \t tabulação horizontal (U+0009). Qualquer outro caractere de controle precisa usar a forma \u00XX, como \u0001.

Repare no que isso significa para quebras de linha em específico: um documento formatado tem quebras de linha reais entre tokens, mas uma quebra de linha dentro de um valor de string é sempre os dois caracteres \n. Um valor de string ocupa uma única linha no arquivo mesmo quando o conteúdo tem várias linhas — um dos pontos de confusão mais comuns ao ler JSON formatado.

EscapePonto de códigoCaractere
\bU+0008backspace
\tU+0009tabulação horizontal
\nU+000Aavanço de linha (nova linha)
\fU+000Cavanço de formulário
\rU+000Dretorno de carro
\u00XXU+0000–U+001Fqualquer outro caractere de controle

Uma nota de três linhas guardada em uma única string JSON

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

O conteúdo dela depois do parsing

first line
second line	tabbed

\uXXXX: qualquer ponto de código, exatamente quatro dígitos hexadecimais

O escape \u escreve um ponto de código como exatamente quatro dígitos hexadecimais: \u00e9 vale é, \u4f60 vale 你, e \u0041 vale o mesmo A que você poderia ter digitado literalmente. Os parsers tratam a forma escapada e a literal como idênticas — um documento que escapa todo caractere não ASCII e um que escapa só os dois obrigatórios decodificam para exatamente as mesmas strings. Escapar é uma decisão de transporte, não uma diferença de dados.

Os quatro dígitos limitam a forma a pontos de código até U+FFFF, o Plano Multilíngue Básico. Tudo acima disso — emojis, escritas históricas, a maioria dos símbolos — não cabe em um único escape \u, e é exatamente nessa limitação que os surrogate pairs entram na história.

Duas grafias do mesmo valor

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

Ambas resultam na mesma string

city: 北京

(o arquivo escapado é só ASCII;
o literal é menor, mas multibyte)

Plano astral: surrogate pairs e surrogates órfãos

Internamente, o JavaScript e a notação \u trabalham em unidades de código UTF-16. Caracteres acima de U+FFFF são escritos como um par: um high surrogate de U+D800–U+DBFF seguido por um low surrogate de U+DC00–U+DFFF. O rosto sorridente U+1F600, portanto, se torna \ud83d\ude00 — doze caracteres no código-fonte para um emoji. Escape uma metade sem a outra e você tem um surrogate órfão: o RFC 8259 §8.2 marca essas strings como imprevisíveis de interpretar, e produtores interoperáveis simplesmente nunca as emitem.

Surrogate pairs também são a razão de o comprimento de string mentir. O JavaScript reporta o comprimento de "😀" como 2, porque conta unidades de código UTF-16 em vez de caracteres — um detalhe que importa quando você fatia, trunca ou valida texto escapado, e o motivo de existirem bibliotecas cientes de grafemas.

Um emoji, duas unidades de código, doze caracteres escapados

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

Ambas resultam no mesmo caractere único

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

O custo do escape: legibilidade e tamanho

Escapar troca legibilidade por segurança. Um documento que escapa todo caractere não ASCII sobrevive a qualquer canal de 7 bits — gateways que removem o oitavo bit de cada byte, sistemas de e-mail legados, logs configurados como ASCII puro — ao preço de ficar quase ilegível para humanos. Essa era a postura padrão de muitas bibliotecas JSON antigas; os padrões modernos preferem UTF-8 literal e escapam apenas o que a gramática exige.

O custo de tamanho é previsível a partir do ponto de código, porque cada escape \uXXXX custa exatamente 6 bytes, independentemente do que codifica. Remover os escapes de um documento com muito CJK o reduz mais ou menos à metade; texto árabe e cirílico encolhe cerca de dois terços; e escapar ASCII puro o faz crescer seis vezes — e é por isso que payloads ASCII escapados de sistemas antigos parecem muito mais gordos do que o conteúdo real deles.

TextoPonto de códigoUTF-8 literalForma escapadaTamanho escapado
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

Remover escapes, round-trips e quando manter os escapes

Remover escapes não é uma operação separada que você precisa escrever: todo parser em conformidade a executa durante a decodificação, então interpretar "\u0041" e interpretar "A" produzem strings idênticas. A direção inversa — escolher o que escapar ao produzir JSON — é onde as bibliotecas divergem no gosto, e onde as únicas regras rígidas são os dois escapes obrigatórios mais a exigência dos caracteres de controle.

Guarde o escape agressivo para os dois casos de uso honestos dele: sobreviver a canais que não merecem confiança com UTF-8 e diffs em que toda diferença de codificação invisível deveria ficar visível. Em todo o resto, UTF-8 literal com escapes mínimos é menor, mais rápido de varrer e mais fácil de revisar. Se um documento recebido parecer com escapes demais, faça o parsing e re-serialize uma vez — a saída descarta todo escape opcional e continua sendo exatamente o mesmo dado.

Perguntas frequentes

É seguro colar strings sensíveis nas ferramentas JSON deste site?

Sim — o formatador, o validador e o minificador linkados deste guia rodam localmente no seu navegador. Não há etapa de envio: o parsing e a re-serialização acontecem em JavaScript dentro da aba, o painel Network não mostra requisição nenhuma levando seu texto, e a Content-Security-Policy do site bloqueia conexões de saída como uma segunda linha de defesa. Suas strings nunca saem da máquina em que você as digitou.

Como colocar um emoji em uma string JSON?

De dois jeitos: cole o emoji literalmente — 😀 é JSON UTF-8 válido — ou escreva o surrogate pair dele, \ud83d\ude00. Ambos decodificam para o mesmo caractere. A forma escapada vale a pena conhecer porque alguns canais e ferramentas antigas estragam emojis literais, e porque mensagens de erro, logs e diffs costumam mostrar o par em vez do rosto.

O que é um surrogate órfão e por que ele quebra parsers?

Um high surrogate (\ud800–\udbff) ou um low surrogate (\udc00–\udfff) escrito sem o par dele. Sozinho, ele não codifica nada: o RFC 8259 §8.2 chama as strings que os contêm de imprevisíveis de interpretar. As implementações variam — algumas os aceitam, algumas os substituem por U+FFFD, algumas lançam erro — então nunca emita metade de um par, e trate um surrogate órfão em dados de entrada como corrupção.

Por que \/ é um escape válido se / não precisa de escape?

Ele existe para embutir JSON em HTML: dentro de um bloco <script>, a sequência literal </script> em uma string encerraria o bloco antes da hora, então geradores escapam cada barra como \/ para quebrar o padrão. A gramática aceita a forma sem escape igualmente. A menos que você esteja embutindo JSON em HTML, \/ é ruído puro — e os minificadores o removem com prazer.

Escapar muda os dados que minha API recebe?

Não. Escapes são grafias, não valores: "\u00e9" e "é" decodificam para a string idêntica, então qualquer parser em conformidade entrega o mesmo texto Unicode de qualquer jeito. Escapar muda apenas a aparência do arquivo e o tamanho dele — e é por isso que uma resposta com escapes demais é feia, mas inofensiva, e por que você pode normalizá-la fazendo o parsing e re-serializando.

Quanto menor fica o JSON sem os escapes \u?

Depende do sistema de escrita, porque cada \uXXXX custa 6 bytes, enquanto o UTF-8 literal custa 1 byte para ASCII, 2 para letras árabes ou cirílicas, 3 para CJK e 4 para emojis. Remover os escapes de um documento chinês o reduz aproximadamente à metade; árabe ou cirílico encolhe cerca de dois terços; escapar ASCII faz crescer seis vezes. A forma escapada é sempre igual ou maior.

Onde posso ler os padrões citados aqui?

  • RFC 8259 — O formato de intercâmbio de dados JavaScript Object Notation (JSON)