JSON RFC 8259: detalhes sutis que derrubam parsers de produção

JSON é menor do que você pensa

A gramática JSON da RFC 8259 é minúscula: objetos, arrays, strings, números e os três literais true, false e null. Essa simplicidade é por que ele está em toda parte — e também por que duas surpresas pegam as pessoas desprevenidas. Primeiro, qualquer valor pode ser o documento inteiro: 42, "hello" e true são, cada um, JSON completo e válido. A RFC 4627 mais antiga exigia que o nível superior fosse um objeto ou array, então algumas ferramentas legadas ainda rejeitam um valor solto.

Segundo, a especificação deixa deliberadamente vários comportamentos não especificados. Ela define a sintaxe com precisão, mas diz pouco sobre chaves duplicadas, precisão numérica ou ordem. Os parsers de produção preenchem essas lacunas de formas diferentes, e é exatamente aí que vivem os bugs de interoperabilidade.

Chaves duplicadas: comportamento indefinido

A RFC 8259 diz que os nomes de objeto DEVERIAM (SHOULD) ser únicos, não que DEVEM (MUST). Quando uma chave se repete, o resultado não é especificado, então os parsers divergem: a maioria pega o último valor, alguns mantêm o primeiro, uns poucos lançam erro e um raro mantém ambos. Nenhum está errado pela especificação.

É um risco de segurança real. Se um serviço valida {"role":"user","role":"admin"} lendo o primeiro role e outro age sobre o último, um atacante pode passar um privilégio pela validação. Trate chaves duplicadas como erro onde a decisão importa, e nunca dependa de qual valor 'vence'.

Números: não há inteiros, só problemas

JSON tem um único tipo de número. A RFC não define precisão nem faixa — apenas observa que, por interoperabilidade, espera-se que as implementações lidem com valores que caibam em dupla precisão IEEE 754. Na prática isso significa que qualquer coisa acima de 2^53 (ou seja, 9007199254740992) pode perder precisão.

O bug clássico: um ID de 64 bits como 9007199254740993 vira 9007199254740992 após JSON.parse em JavaScript, porque os números são doubles. A solução é carregar inteiros grandes como strings. Mais algumas regras que as pessoas esquecem: sem zeros à esquerda (007 é inválido), sem + inicial, sem ponto decimal final (1. é inválido), e NaN e Infinity não são JSON válido — serialize-os como null ou string.

Strings, Unicode e codificação

As strings devem usar aspas duplas, e caracteres de controle de U+0000 a U+001F devem ser escapados. A barra pode ser opcionalmente escapada como \/, por isso alguns codificadores emitem <\/script> — uma forma legal de não quebrar uma tag script de HTML. A RFC 8259 obriga UTF-8 para o JSON trocado entre sistemas.

Caracteres fora do Plano Multilíngue Básico (BMP), como a maioria dos emoji, são escritos como um par substituto (surrogate) de escapes \u, e não como um único. Substitutos solitários, sem par, são tecnicamente inválidos, mas alguns parsers os aceitam — uma armadilha de portabilidade que só aparece quando seus dados cruzam para uma implementação mais estrita.

Coisas que parecem JSON mas não são

Os erros mais comuns vêm de tratar JSON como JavaScript. Vírgulas finais são inválidas. Comentários não existem em JSON — JSON5 e JSONC os adicionam, mas um parser estrito da RFC 8259 vai rejeitá-los. Strings com aspas simples e chaves de objeto sem aspas também são inválidas, por mais familiares que pareçam.

Mais sutil é a marca de ordem de bytes (BOM). A RFC 8259 diz que produtores NÃO DEVEM (MUST NOT) adicionar uma BOM e consumidores PODEM (MAY) ignorá-la, mas a BOM não faz parte do JSON, então uma BOM UTF-8 no início de um arquivo pode fazer um parser estrito falhar no primeiro byte. O espaço em branco insignificante limita-se a espaço, tabulação, quebra de linha e retorno de carro — nada mais.

Conclusões

Rejeite chaves duplicadas onde uma decisão de segurança ou correção dependa do valor.

Carregue inteiros de 64 bits como strings para que sobrevivam a parsers de dupla precisão como o do JavaScript.

Não dependa da ordem das chaves de objeto — a especificação define objetos como não ordenados, mesmo que a maioria dos parsers preserve a ordem de inserção.

Lembre que qualquer valor é JSON de nível superior válido sob a RFC 8259, não só objetos e arrays.

Se você quer comentários ou vírgulas finais, você quer JSON5/JSONC — converta antes que um parser estrito veja os dados. O formatador JSON deste site valida contra a gramática estrita da RFC 8259, então esses problemas aparecem de imediato.