Modo Estrito e Conformidade com RFC 8259 na Validação JSON
Por Aerisium Core · ·
A gramática JSON definida na RFC 8259 é um subconjunto estrito da sintaxe de object literals do JavaScript. Ambas parecem quase idênticas, mas as diferenças causam sistematicamente falhas em produção: dados que são parseados sem erro no navegador de um desenvolvedor quebram silenciosamente consumidores downstream que executam parsers compatíveis. Entender exatamente onde as gramáticas divergem é crítico para construir pipelines de dados confiáveis.
Object Literals do JavaScript versus a Gramática JSON
A sintaxe de objetos do JavaScript evoluiu através das especificações ECMAScript, acumulando conveniências que JSON exclui deliberadamente. A RFC 8259 formaliza uma gramática restrita sem sintaxe opcional.
As incompatibilidades críticas são:
-
Trailing commas: JavaScript permite
{ "a": 1, "b": 2, }. A gramática JSON rejeita qualquer vírgula após o último par chave-valor. O parser espera uma vírgula apenas entre pares: após o último par, o próximo token deve ser a chave de fechamento. -
Strings com aspas simples: JavaScript aceita
'key'e"key"indistintamente. A seção 7 da RFC 8259 determina que strings sejam delimitadas exclusivamente por aspas U+0022. Aspas simples (U+0027) não têm significado na gramática léxica do JSON. -
Chaves sem aspas: JavaScript permite
{ key: "value" }ondekeyé tratado como um identificador. A gramática JSON exige que toda chave seja uma string entre aspas. -
Comentários: Objetos JavaScript podem conter comentários
//e/* */. A gramática JSON não possui produção de comentários. A barra (/) aparece apenas em sequências de escape. -
Undefined e NaN:
JSON.stringifydo JavaScript remove silenciosamente valoresundefinede converteNaNparanull. Um parser que aceita esses valores como entrada produz dados que não sobrevivem a um round-trip através deJSON.parse.
Trailing Commas em Pipelines de Produção
A aceitação de trailing commas é a falha de validação JSON mais comum em sistemas reais. Considere um arquivo de configuração consumido por um processador de fluxo baseado em Java:
{
"queue_batch_size": 1000,
"retry_policy": "exponential_backoff",
"dead_letter_topic": "dlq.primary",
}
A trailing comma após "dead_letter_topic" faz com que qualquer parser compatível com RFC 8259, como com.google.gson.stream.JsonReader ou com.fasterxml.jackson.core.JsonParser, lance uma exceção de parse. O processador de fluxo falha na inicialização. O deploy falha. O incidente custa horas de engenharia para diagnosticar porque o mesmo JSON é renderizado sem erro em todos os formatadores baseados em navegador que toleram trailing commas.
A causa raiz é um descompasso entre as ferramentas de desenvolvimento e o parse em produção. Desenvolvedores que trabalham no navegador nunca veem o erro porque JSON.parse() do V8 é estritamente compatível com RFC desde o ES5 e também rejeita trailing commas. A desconexão ocorre quando ferramentas intermediárias — editores, formatadores ou scripts personalizados— aceitam silenciosamente a entrada malformada antes que os dados cheguem ao parser de produção. Quando o parser estrito a rejeita, o artefato já foi implantado.
Validação em Streaming versus Construção de AST
Validar um documento JSON contra a RFC 8259 pode ser realizado em dois níveis, cada um com complexidade computacional distinta.
Um lexer em streaming executa em tempo O(n) e memória O(1). O parser classifica cada caractere sequencialmente: espaços em branco são descartados, tokens estruturais ({, }, [, ], :, ,) são validados nas posições esperadas, strings são escaneadas em busca de caracteres de controle sem escape, e números são verificados contra a gramática numérica (-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?). O lexer rejeita entrada malformada no primeiro caractere inválido e termina imediatamente.
A construção completa de AST requer espaço O(n + m), onde m é o número de nós alocados. Um documento profundamente aninhado como {"a":{"a":{"a":{"a":null}}}} aloca um novo descritor de objeto no heap do V8 para cada nível de aninhamento. Um array plano de 10 MB de primitivas produz um AST pequeno: um nó array mais N nós primitivos. Um documento de 10 MB de objetos profundamente aninhados com nomes de chave longos pode exigir de 8 a 12 vezes o tamanho bruto em bytes na memória do heap.
// Validador em streaming O(n) — sem AST, memória O(1)
function validateJson(input) {
let i = 0;
let depth = 0;
let state = 'VALUE';
while (i < input.length) {
const ch = input[i];
switch (state) {
case 'VALUE':
if (ch === '{' || ch === '[') { depth++; state = ch === '{' ? 'KEY' : 'VALUE'; i++; }
else if (ch === '"') { state = 'STRING_END'; i++; }
else if (ch === 't') { if (input.slice(i, i + 4) === 'true') { i += 4; state = 'SEPARATOR'; } else return false; }
else if (ch === 'f') { if (input.slice(i, i + 5) === 'false') { i += 5; state = 'SEPARATOR'; } else return false; }
else if (ch === 'n') { if (input.slice(i, i + 4) === 'null') { i += 4; state = 'SEPARATOR'; } else return false; }
else if (ch === '-' || (ch >= '0' && ch <= '9')) { state = 'NUMBER'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'KEY':
if (ch === '"') { state = 'KEY_END'; i++; }
else if (ch === '}') { depth--; state = 'SEPARATOR'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'KEY_END':
if (ch === '"') { state = 'COLON'; i++; }
else if (ch === '\\') { i += 2; }
else if (ch >= ' ') { i++; }
else return false;
break;
case 'COLON':
if (ch === ':') { state = 'VALUE'; i++; }
else if (ch === ' ') { i++; }
else return false;
break;
case 'STRING_END':
if (ch === '"') { state = 'SEPARATOR'; i++; }
else if (ch === '\\') { i += 2; }
else if (ch >= ' ') { i++; }
else return false;
break;
case 'NUMBER':
if (ch >= '0' && ch <= '9') { i++; }
else if (ch === '.' || ch === 'e' || ch === 'E' || ch === '-' || ch === '+') { i++; }
else if (ch === ' ' || ch === ',' || ch === '}' || ch === ']') { state = ch === ',' ? 'VALUE' : 'SEPARATOR'; }
else return false;
break;
case 'SEPARATOR':
if (ch === ',') { state = 'VALUE'; i++; }
else if (ch === '}' || ch === ']') { depth--; i++; }
else if (ch === ' ') { i++; }
else if (i >= input.length) { /* fim */ }
else return false;
break;
}
}
return depth === 0;
}
Um validador em streaming atinge tempo O(n) e memória O(1), tornando-o adequado para validar payloads de vários gigabytes sem exaustão do heap. A contrapartida é a notificação de erros: um validador em streaming não pode informar o contexto estrutural preciso de um erro (por exemplo, “esperava-se uma vírgula na linha 42.372, coluna 15”) sem manter metadados de posição, o que empurra a complexidade para O(log n) para um rastreador de linhas.
Prevenção de Falhas Silenciosas em CI/CD
A classe mais perigosa de falha de validação JSON ocorre quando um pipeline CI/CD usa um parser permissivo durante a compilação e um parser estrito em tempo de execução.
Considere um pipeline de deploy onde um arquivo de configuração TypeScript é processado por ts-node durante a compilação, que avalia o arquivo como JavaScript, aceitando trailing commas e aspas simples. O artefato de saída é escrito como um arquivo .json. O serviço em produção lê o artefato usando encoding/json do Go, que é compatível com RFC 8259.
O artefato passa na validação de compilação porque ts-node nunca invoca JSON.parse() sobre a saída. Os valores de configuração estão corretos. A compilação é bem-sucedida. O artefato é implantado. A falha se manifesta apenas quando o serviço de produção tenta revalidar ou transformar o artefato, ou quando um sistema externo o consome. Nesse ponto o artefato já foi implantado e a reversão exige um ciclo completo de deploy.
A solução é aplicar a validação RFC 8259 no ponto mais cedo possível do pipeline: um hook de pre-commit ou uma etapa de compilação, usando um parser que corresponda ao comportamento do runtime de produção. Qualquer divergência entre o parse em tempo de compilação e em tempo de execução é dívida técnica que eventualmente se materializará como um incidente de produção. O validador confiável mais simples é o JSON.parse(): está incorporado em todos os runtimes modernos, é estritamente compatível com RFC e garante-se que corresponda a qualquer parser que acompanhe seu ambiente de produção.