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.

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:
- Objetos usam chaves, os membros são separados por vírgulas, e uma vírgula depois do último membro não é permitida;
- Arrays usam colchetes, os elementos são separados por vírgulas, e vírgulas finais também não são permitidas;
- Números não admitem
+à frente, nem zeros à esquerda (01é inválido), nem ponto decimal solto (1.é inválido), nemNaNouInfinity; - Strings precisam usar aspas duplas; aspas simples são inválidas;
- Booleanos e null são literais minúsculos;
TrueeNULLsão ambos inválidos; - Só caracteres de espaço em branco
0x20(espaço),0x09(tabulação),0x0A(quebra de linha) e0x0D(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,\te\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:
- O texto JSON usado para intercâmbio deve ser codificado em UTF-8;
- As implementações não devem adicionar um BOM (
EF BB BF) no início do texto; - 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.parsecomUnexpected 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.parsemanté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/jsonsemcharset; - [ ] Os pipelines de leitura/gravação de arquivos removem o BOM e usam UTF-8 explícito;
- [ ] Inteiros acima de
2^53sã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.