JSON Toolkit

Lendo os limites da gramática JSON na RFC 8259

Resumo: Traduzindo as produções gramaticais da RFC 8259 para linguagem simples: quais caracteres podem aparecer diretamente, quais precisam ser escapados e onde a especificação deixa margem de propósito.

Iniciante~11 min de leituraPublicado em 2026-10-30Atualizado em 2026-10-30
Capa de Lendo os limites da gramática JSON na RFC 8259

Resumo da ópera: quando aparecer um erro de sintaxe JSON, encontre o primeiro caractere ilegal e classifique-o como aspa simples, vírgula final, comentário ou caractere de controle em vez de reler o documento inteiro; do lado da codificação, confirme UTF-8 sem BOM e transporte inteiros acima de 2^53 - 1 como strings.

O corpo da especificação tem só uma dúzia de páginas, mas atrás de cada "MUST", "SHOULD" e "MAY" se esconde um incidente que já deixou uma equipe acordada a noite toda.

1. Como os três textos se relacionam

Aprender JSON costuma ficar confuso por causa de três números: RFC 4627, RFC 7159, RFC 8259, além da ECMA-404. A relação, na verdade, é bem clara:

Texto Data Situação Diferença principal
RFC 4627 2006 Substituída O nível superior precisa ser um objeto ou um array
RFC 7159 2014 Substituída Nível superior relaxado para qualquer valor JSON
RFC 8259 2017.12 Padrão atual UTF-8 explícito, orientações de interoperabilidade adicionadas
ECMA-404 2013, segunda edição 2017.12 Especificação de gramática Define apenas a gramática, não cobre codificação nem transporte

Qual ler? A RFC 8259 sozinha basta. A ECMA-404 é equivalente na parte de gramática; a diferença é que a RFC 8259 também especifica "transporte como UTF-8" e "os problemas de interoperabilidade que os implementadores devem observar".

2. Seis tipos de valor

As produções gramaticais da especificação cabem em um único diagrama:

JSON-text  = ws value ws
value      = object / array / number / string / "true" / "false" / "null"
object     = "{" [ member *( "," member ) ] "}"
member     = string ws ":" ws value
array      = "[" [ value *( "," value ) ] "]"

São só seis pontos-chave:

  1. Objetos usam chaves, os membros são separados por vírgulas, e uma vírgula depois do último membro não é permitida;
  2. Arrays usam colchetes, os elementos são separados por vírgulas, e vírgulas finais também não são permitidas;
  3. Números não admitem + à frente, nem zeros à esquerda (01 é inválido), nem ponto decimal solto (1. é inválido), nem NaN ou Infinity;
  4. Strings precisam usar aspas duplas; aspas simples são inválidas;
  5. Booleanos e null são literais minúsculos; True e NULL são ambos inválidos;
  6. Só caracteres de espaço em branco 0x20 (espaço), 0x09 (tabulação), 0x0A (quebra de linha) e 0x0D (retorno de carro) podem aparecer entre elementos estruturais.

O trecho abaixo é JSON válido, embora muita gente ache que está errado na primeira vez que o vê — porque é uma string crua no nível superior:

"just a string"

Desde a RFC 7159, o nível superior realmente pode ser qualquer valor. Em APIs, porém, recomenda-se ainda usar apenas objetos ou arrays; o motivo está na seção 6.

3. O que pode realmente aparecer diretamente dentro de uma string

Esta é a parte em que é mais fácil ser enganado por "na minha máquina funciona". As regras da especificação para strings são:

  • Caracteres permitidos diretamente: %x20-21, %x23-5B, %x5D-10FFFF;
  • Em outras palavras, a aspa dupla " (0x22) e a barra invertida \ (0x5C) precisam ser escapadas;
  • Todos os caracteres de controle abaixo de 0x20 precisam ser escapados, incluindo quebra de linha, tabulação e retorno de carro;
  • As únicas sequências de escape disponíveis são \", \\, \/, \b, \f, \n, \r, \t e \uXXXX.
{
  "ok": "Texto em chinês, emoji 🎉 e símbolos de largura total podem ser escritos diretamente",
  "escaped": "Uma quebra de linha entra aqui\n e uma tabulação\ttermina",
  "control": "\u0007 é o caractere de campainha e precisa ser escapado",
  "quote": "Ele disse: \"não use aspas simples\""
}

\/ é um escape legado (evita que </script> feche cedo demais). Cenários modernos raramente precisam dele, mas os analisadores precisam suportá-lo.

4. Codificação: UTF-8, sem BOM

A seção 8.1 da RFC 8259 diz três coisas:

  1. O texto JSON usado para intercâmbio deve ser codificado em UTF-8;
  2. As implementações não devem adicionar um BOM (EF BB BF) no início do texto;
  3. Os analisadores podem ignorar o BOM para serem mais robustos.

Duas falhas típicas no mundo real:

  • O Bloco de Notas do Windows salva como "UTF-8 com BOM", e o arquivo é rejeitado pelo JSON.parse com Unexpected token;
  • Um arquivo JSON em chinês salvo como GBK vira texto embaralhado em um pipeline que decodifica apenas como UTF-8, mas a sintaxe ainda passa por acaso, então o erro é adiado para a camada de negócio.

O tratamento é simples: remova o BOM ao ler o arquivo e decodifique explicitamente como UTF-8; ao gravar, garanta que nenhum BOM seja adicionado.

/**
 * Remove o BOM UTF-8 no início de uma string.
 * @param text O texto original
 * @returns O texto sem BOM
 */
function stripBom(text: string): string {
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}

Uma observação sobre tipos de mídia, já que estamos aqui: application/json não aceita mais o parâmetro charset; escrevê-lo não gera erro, mas é uma prática ultrapassada.

5. Três pontos em que a especificação deixa margem de propósito

Os pontos em que a especificação se recusa a fixar detalhes são exatamente de onde vêm as diferenças entre implementações.

1. Faixa e precisão dos números

A RFC 8259 só diz que "esta especificação não limita a faixa nem a precisão dos números", observando que a maioria das implementações usa precisão dupla IEEE 754. Isso significa que o mesmo 9223372036854775807 vira 9223372036854776000 em JavaScript, mas continua exato no long do Java.

Conclusão: inteiros acima de 2^53 - 1 e valores monetários que exigem muitas casas decimais de precisão devem sempre ser transportados como strings, com o pattern escrito explicitamente no schema.

2. Chaves duplicadas

O texto da especificação é "os nomes dentro de um objeto devem (SHOULD) ser únicos"; ele não diz o que fazer quando não são.

  • JSON.parse mantém a última;
  • Alguns analisadores mantêm a primeira;
  • Algumas bibliotecas simplesmente geram erro.

Conclusão: não escreva chaves duplicadas e não conte com nenhum comportamento específico nos seus validadores. Adicionar uma etapa de "detecção de chave duplicada" no build é bem mais barato do que investigar depois.

3. Limites de profundidade e comprimento

A especificação não define limite de profundidade de aninhamento, mas toda implementação tem um. O V8 lança RangeError: Maximum call stack size exceeded em dezenas de milhares de níveis de aninhamento. Os servidores precisam impor seus próprios limites; isso é um requisito de segurança, não da especificação.

6. Testando o entendimento com contraexemplos

Cada linha abaixo é JSON inválido. Entregue-as a um analisador e veja se as mensagens de erro são úteis:

{ 'name': 'json' }        // aspas simples
{ name: "json" }          // nome de chave sem aspas
{"a": 1,}                 // vírgula final
[1, 2, 3,]                // vírgula final em um array
{"a": 01}                 // zero à esquerda
{"a": +1}                 // sinal de mais à frente
{"a": .5}                 // parte inteira ausente
{"a": NaN}                // literal não padrão
{"a": undefined}          // não é um valor JSON
{"a": 1} // comment       // comentário

Um caso de fronteira interessante: {"a": 1} seguido de um comentário é válido em JSON5, válido em JSONC e inválido em JSON padrão. Então "o arquivo de configuração roda, mas a API rejeita" quase sempre é um problema de dialeto.

7. Lista de verificação para produção

  • [ ] Todo JSON transmitido externamente usa application/json sem charset;
  • [ ] Os pipelines de leitura/gravação de arquivos removem o BOM e usam UTF-8 explícito;
  • [ ] Inteiros acima de 2^53 são transportados como strings e anotados no schema;
  • [ ] Chaves duplicadas são detectadas no build;
  • [ ] O servidor limita o tamanho do corpo da requisição e a profundidade de aninhamento;
  • [ ] Os valores de nível superior de APIs públicas são objetos ou arrays, nunca escalares crus.

Perguntas frequentes

Como saber rapidamente se um texto é JSON válido?

Entregue o texto a um analisador ou a uma ferramenta de análise online, leia a posição do primeiro caractere ilegal e confira com os seis tipos de valor e as regras de espaço em branco da RFC 8259. O nível superior pode ser qualquer valor JSON, mas entre elementos estruturais só são permitidos espaço, tabulação, quebra de linha e retorno de carro. Aspas simples, chave sem aspas, vírgula final ou comentário significa que não é JSON padrão.

Por que o JSON precisa usar UTF-8 e não pode ter BOM?

A seção 8.1 da RFC 8259 exige que o texto JSON usado para intercâmbio seja codificado em UTF-8, diz que as implementações não devem adicionar BOM no início e permite que os analisadores ignorem o BOM por robustez. Um arquivo com BOM é rejeitado pelo JSON.parse com Unexpected token, embora nada pareça errado a olho nu. Por isso, remova o BOM ao ler e decodifique explicitamente como UTF-8.

Devo usar JSON5 ou JSONC para contornar esses limites de gramática?

Sim, mas apenas para leitura local, como arquivos de configuração; dados trocados entre sistemas ainda devem ser JSON padrão. Os contraexemplos mostram que {"a": 1} seguido de um comentário é válido em JSON5 e JSONC, mas inválido em JSON padrão, o que explica por que "o arquivo de configuração roda, mas a API rejeita" quase sempre é um problema de dialeto. Ao converter, primeiro remova comentários, aspas simples e vírgulas finais e então entregue o resultado a um analisador padrão.