JSON5 e JSONC: JSON com comentários
Resumo: Os dois dialetos existem porque configuração escrita por humanos precisa de comentários e vírgulas finais, mas a sintaxe e o alcance no ecossistema diferem, então escolher errado trava sua cadeia de ferramentas.

Resumo da ópera: use JSONC para comentários dentro da configuração do VS Code e do TypeScript, use JSON5 quando precisar da sintaxe de literal ES5.1 mais ampla, como aspas simples ou chaves sem aspas, e mantenha as APIs públicas em JSON padrão.
O JSON padrão não permite comentários, então toda equipe os escreve discretamente mesmo assim — até que um dia o build quebra. Em vez de fazer isso em segredo, escolha um dialeto adequado.
1. Os dois dialetos têm motivações diferentes
- JSONC (JSON with Comments): JSON padrão mais comentários e vírgulas finais, com quase nenhuma outra mudança de sintaxe. É o padrão de fato do ecossistema do VS Code —
settings.json,tsconfig.jsone.eslintrc.jsontodos o usam. - JSON5: bem mais ambicioso, alinhado diretamente com o literal de objeto ES5.1 — aspas simples, chaves sem aspas, hexadecimal,
Infinity/NaN, continuação de linha com barra invertida e sinais+à frente.
Uma frase para distingui-los: JSONC só deixa a escrita confortável; JSON5 permite escrever um arquivo de configuração como se fosse JavaScript.
2. Tabela comparativa de sintaxe
| Recurso | JSON padrão | JSONC | JSON5 |
|---|---|---|---|
Comentários // /* */ |
✗ | ✓ | ✓ |
| Vírgulas finais | ✗ | ✓ | ✓ |
| Strings com aspas simples | ✗ | ✗ | ✓ |
| Chaves sem aspas | ✗ | ✗ | ✓ |
| Números hexadecimais | ✗ | ✗ | ✓ |
Ponto decimal no início/fim .5 5. |
✗ | ✗ | ✓ |
Números começando com + |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
Continuação com \ no fim da linha |
✗ | ✗ | ✓ |
| Strings multilinha | ✗ | ✗ | ✓ (implementadas por continuação) |
| Valor de nível superior arbitrário | ✓ | ✓ | ✓ |
A especificação completa do JSON5 não passa de uma página; o pacote json5 é mantido pela comunidade há muito tempo, e navegadores e Node se comportam de forma idêntica.
3. Análise: duas APIs, dois tipos de capacidade de posicionamento
3.1 JSON5: análise simples do documento inteiro
import JSON5 from 'json5';
const config = JSON5.parse(`
{
// porta do serviço
port: 8080,
host: '0.0.0.0',
features: ['json5', 'jsonc',],
retries: 0x10,
}
`);
console.log(config.port, config.retries); // 8080 16
JSON5.parse retorna um objeto simples sem informação de offset — porque não é voltado a cenários de edição, apenas a "ler configuração".
3.2 JSONC: uma árvore de sintaxe com offsets
O valor do jsonc-parser não está na análise, mas na edição. Ele oferece três camadas de API:
| API | Finalidade | Uso típico |
|---|---|---|
parse |
Analisar em uma árvore carregando offset/length |
Realce de sintaxe, dobramento, navegação |
visit |
Travessia em profundidade; o callback recebe o caminho do nó | Inspeção no estilo JSONPath |
modify + applyEdits |
Produzir uma edição mínima de texto e aplicá-la ao texto original | Mudar um valor mantendo comentários |
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';
const text = `{
// configuração de porta
"server": {
"port": 8080 // 80 em produção
}
}`;
const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);
const edits = modify(text, ['server', 'port'], 3000, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));
A saída mantém os comentários originais e substitui apenas os três caracteres de 8080:
{
// configuração de porta
"server": {
"port": 3000 // 80 em produção
}
}
Isso é exatamente o que "editar configuração com expressões regulares" não consegue fazer: um regex não sabe qual 8080 mudar nem que o 8080 dentro de uma string precisa ficar intocado.
3.3 Mensagens de erro precisam carregar offsets
Os códigos de erro do jsonc-parser são valores de enum; os comuns são:
| Código de erro | Significado |
|---|---|
InvalidSymbol |
Apareceu um caractere que não deveria estar ali |
PropertyNameExpected |
Esperava um nome de chave |
ColonExpected |
Esperava dois-pontos |
CommaExpected |
Esperava vírgula |
CloseBraceExpected |
O objeto não foi fechado |
UnexpectedEndOfComment |
O comentário não foi fechado |
InvalidEscapeCharacter |
Escape inválido |
Uma vez convertido offset em linha e coluna, o editor consegue desenhar a marca ondulada exatamente na posição problemática.
4. Quando usar qual
| Cenário | Recomendação | Motivo |
|---|---|---|
| Corpos de requisição/resposta de APIs públicas | JSON padrão | Dialetos não são suportados por clientes generalistas |
package.json, composer.json |
JSON padrão | Gerenciadores de pacotes só entendem JSON padrão |
tsconfig.json, .vscode/settings.json |
JSONC | Padrão do ecossistema, suportado nativamente pela cadeia de ferramentas |
| Configuração de aplicação (precisa de comentários) | JSONC ou YAML | Comentários precisam ser preservados enquanto ferramentas ainda conseguem ler e gravar |
Precisar de Infinity, hexadecimal, literais complexos |
JSON5 | O maior poder de expressão |
| Troca de dados, logs, caches | Delimitado por linha ou JSON padrão | Streaming e universalidade são necessários |
Regra geral: se o arquivo será lido por uma "ferramenta não humana", prefira JSON padrão; só considere um dialeto quando tiver certeza de que ele é lido apenas pelo seu próprio código.
5. Três tipos de risco que os dialetos introduzem
5.1 Transbordo do requisito
Quando outra ferramenta lê um arquivo de configuração JSON5 e chama JSON.parse, ela quebra de vez. O sintoma típico é "funciona bem localmente, falha no Docker" — porque o container executa um caminho de leitura diferente.
Contramedida: adicione ao repositório uma verificação de CI que liste explicitamente quais arquivos podem usar um dialeto; todo o resto é validado como JSON padrão.
5.2 Surpresas em torno da inferência de tipo
JSON5 permite chaves sem aspas e aspas simples, mas não permite trazer construções literais como datas ou expressões regulares — ele continua sendo um formato de dados, não avaliação JavaScript.
Pessoas já presumiram que JSON5 conseguiria expressar new Date() só para ver a análise falhar. JSON5 não é eval.
5.3 Comentários descartados silenciosamente
Nem JSON5.stringify nem JSON.stringify preservam comentários. Então "ler, ajustar um valor, gravar de volta" perde todos os comentários.
Quando comentários precisam sobreviver, as únicas opções são a API de edição do jsonc-parser ou uma biblioteca YAML que suporte preservação de comentários.
6. Recomendações práticas
- [ ] Mantenha uma tabela "tipo de arquivo → formato" na documentação da equipe para que ninguém improvise;
- [ ] Envolva a leitura de configuração em um único
loadConfig()que decide internamente qual analisador usar, para que o código de negócio nunca chameJSON.parsediretamente; - [ ] Ao modificar programaticamente uma configuração com comentários, use
modify+applyEdits; nunca reescreva o arquivo inteiro; - [ ] Valide também arquivos de dialeto no CI (
jsonc-parserpara JSONC,JSON5.parsepara JSON5), caso contrário erros de sintaxe só aparecem em tempo de execução; - [ ] Use JSON padrão para interfaces públicas, chaves de cache e payloads de fila de mensagens.
Perguntas frequentes
Como devo escolher entre JSON5 e JSONC?
Escolha pelo runtime em vez de pela contagem de sintaxe: VS Code, TypeScript e ESLint leem JSONC por padrão, então JSONC dá a melhor compatibilidade; recorra ao JSON5 apenas quando precisar da sintaxe de literal ES5.1 mais ampla, como aspas simples, chaves sem aspas ou Infinity. Nenhum dos dialetos é adequado para respostas de APIs públicas.
Por que um analisador JSON padrão não consegue ler JSONC diretamente?
JSONC permite comentários e vírgulas finais, enquanto JSON.parse aceita apenas JSON da RFC 8259 e lança erro em ambos. Ler JSONC exige um analisador como jsonc-parser, ou remover os comentários antes de entregar o texto a um analisador padrão.
Os comentários em JSONC devem sobreviver a edições no arquivo?
Sim, mas apenas por uma API de edição. modify e applyEdits do jsonc-parser mudam o nó alvo preservando os demais comentários; analisar em um objeto e serializar descarta todos os comentários.