As fronteiras da conversão JSON ↔ YAML e XML
Resumo: A afirmação de que JSON é um subconjunto de YAML só garante uma direção, e o XML não tem mapeamento JSON oficial nenhum — tudo depende de convenção.

Resumo da ópera: use JSON como formato intermediário: analise a origem em um modelo de dados e depois serialize para o destino; antes de converter, decida se comentários, atributos e namespaces XML e tipos numéricos ou booleanos precisam sobreviver.
Se um pipeline de conversão é utilizável não depende de quão bonita fica a saída, mas de você estar ciente de quanto ele jogou fora.
1. JSON e YAML: contenção unidirecional
A especificação YAML 1.2 diz claramente: JSON é um subconjunto de YAML. Ou seja, qualquer JSON válido é YAML válido:
{"name": "json", "tags": ["a", "b"], "count": 3}
Este trecho é tanto JSON quanto YAML em estilo flow. O inverso não vale.
| Recurso | JSON | YAML 1.2 |
|---|---|---|
| Comentários | ✗ | ✓ |
| Âncoras e aliases | ✗ | ✓ |
Múltiplos documentos (---) |
✗ | ✓ |
| Chaves não string | ✗ | ✓ (1: a, true: b) |
| Chaves complexas | ✗ | ✓ (? [a, b]) |
| Sensível à indentação | ✗ | ✓ (indentação com tabulação é rejeitada) |
| Inferência de tipo | explícita | implícita (0o17, 0x1F, .inf) |
| Legibilidade em uma linha | boa | ruim (conteúdo longo depende de sintaxe de dobramento) |
1.1 A inferência de tipo é a maior armadilha
O YAML interpreta alguns valores que parecem strings como outros tipos:
zip: 01234 # string ou inteiro octal? o schema core do YAML 1.2 trata como string
version: 1.10 # 1.1 (um float); a semântica é perdida
country: NO # string no YAML 1.2; booleano false no YAML 1.1
date: 2026-10-30 # algumas implementações interpretam como objeto de data
O exemplo NO já queimou muita gente: com um analisador YAML 1.1 (algumas bibliotecas antigas), country: NO vira false.
Contramedida: coloque entre aspas todo identificador que "parece número ou booleano", como em version: "1.10".
1.2 Âncoras, aliases e múltiplos documentos
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Depois de expandir as âncoras e converter para JSON você normalmente fica com dois objetos de configuração duplicados, ou com um array (múltiplos documentos precisam virar [{...}, {...}]).
De qualquer forma, as âncoras não são restauradas na conversão inversa — essa perda é inevitável.
1.3 Conversão que preserva comentários
A maioria das bibliotecas simplesmente descarta comentários durante a conversão. Quando você precisa mantê-los, use a Document API do pacote yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Configuração do serviço
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Configuração do serviço
// port: 3000
O teste é simples: se este arquivo vai ser editado por humanos repetidas vezes, não siga o caminho "analisar → objeto → serializar".
1.4 Como verificar a consistência de ida e volta
/**
* Verifica a consistência de ida e volta de JSON → YAML → JSON.
* @param sourceValue Valor JSON original
* @param stringify Função de serialização YAML
* @param parse Função de análise YAML
* @returns Se os dois resultados são exatamente iguais
*/
function isRoundTripStable(
sourceValue: unknown,
stringify: (input: unknown) => string,
parse: (input: string) => unknown,
): boolean {
const yamlText = stringify(sourceValue);
const restored = parse(yamlText);
return JSON.stringify(restored) === JSON.stringify(sourceValue);
}
Executá-la com dados reais vale mais do que ler a documentação dez vezes. Garanta que seu conjunto de amostras cubra: null, a string vazia, o array vazio, "00", "1e5", zero negativo, inteiros maiores que 2^53 e strings longas com quebras de linha.
2. JSON e XML: não existe mapeamento padrão
O XML é bem anterior ao JSON e tem capacidades estruturais bem mais fortes (atributos, namespaces, conteúdo misto, DTD). Entre os dois não existe especificação oficial de mapeamento; toda ferramenta faz a mesma coisa: escolhe uma convenção e tenta não perder muito.
2.1 Convenções comuns
| Convenção | Representação | Exemplo |
|---|---|---|
| Elemento → chave de objeto | Um elemento de mesmo nome virou campo | <a>1</a> → {"a": "1"} |
Atributo → prefixo @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Texto → #text |
Quando há atributo e texto ao mesmo tempo | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Elemento repetido → array | Duas ou mais ocorrências viram array | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Namespace → prefixo | ns:tag |
<ns:tag/> → {"ns:tag": ...} |
<order id="A1001">
<item sku="X1" qty="2">笔记本</item>
<item sku="X2" qty="1">钢笔</item>
</order>
{
"order": {
"@id": "A1001",
"item": [
{ "@sku": "X1", "@qty": "2", "#text": "笔记本" },
{ "@sku": "X2", "@qty": "1", "#text": "钢笔" }
]
}
}
2.2 Cinco perdas inevitáveis
- Ordem dos elementos: objetos JSON são não ordenados, enquanto XML é uma sequência ordenada de elementos;
- Atributos versus elementos filhos: eles carregam semânticas diferentes em XML, mas ambos viram campos simples em JSON;
- Conteúdo misto: estruturas como
<p>你好 <b>世界</b> 再见</p>não podem ser expressas com um objeto simples; - Declarações de namespace:
xmlnsé um conceito de escopo; uma vez achatado em campos, o escopo se perde; - CDATA e entidades:
<![CDATA[...]]>e&não passam de texto em JSON.
2.3 O dilema de "um elemento ou um array"
<list><item>1</item></list>
Com um único item, o xml2js retorna um objeto por padrão e só retorna um array quando há dois.
Como resultado, o código do frontend precisa escrever Array.isArray(x) ? x : [x].
Contramedida: habilite a opção "sempre array" ao analisar, ou declare o campo como tipo array com base em um schema.
import { parseStringPromise } from 'xml2js';
const parsed = await parseStringPromise(xmlText, {
explicitArray: true, // retorna um array mesmo para um único elemento
trim: true,
explicitRoot: false,
});
2.4 Lembrete de segurança
Antes de analisar XML não confiável, você precisa desabilitar entidades externas, caso contrário pode acionar XXE (leitura de arquivos locais, sondagem de redes internas):
// No ecossistema Node, DTDs e entidades externas geralmente são desabilitados por opções do analisador
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON é apenas a primeira camada da conversão; converter não elimina a superfície de ataque que o XML traz.
3. Onde cada um dos três formatos se encaixa
| Cenário | Formato recomendado | Motivo |
|---|---|---|
| APIs HTTP públicas | JSON | Suporte nativo em navegadores e em toda linguagem |
| Configuração mantida à mão | YAML | Comentários, indentação, legibilidade |
| Documentos que precisam de schema forte e namespaces (SOAP, Office Open XML, SVG) | XML | Capacidades estruturais e o ecossistema existente |
| Interação com ferramentas operacionais (K8s, CI) | YAML | Padrão do ecossistema |
| Persistência, caches, logs | JSON / JSONL | Universal e sem dependências |
4. Notas de implementação
import { stringify as toYaml } from 'yaml';
/**
* Converte um valor JSON em texto YAML mantendo caracteres chineses sem escape.
* @param value O valor JSON a converter
* @returns O texto YAML
*/
function jsonToYaml(value: unknown): string {
return toYaml(value, {
indent: 2,
lineWidth: 0, // desativa a quebra automática para longas strings não serem divididas
defaultStringType: 'QUOTE_DOUBLE',
});
}
Algumas opções práticas:
lineWidth: 0desliga a quebra automática; caso contrário, URLs longas são divididas entre linhas e ficam difíceis de ler;- colocar tudo entre aspas duplas evita que valores como
NOe1.10sejam mal interpretados; - para dados com precisão numérica sensível, converta para strings antes de serializar.
Para a direção XML, fixe uma única função de mapeamento e coloque "prefixo de atributo", "nome da chave de texto" e "estratégia de array" na assinatura dela, em vez de espalhá-los por aí:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Lista de verificação
- [ ] Todo identificador "parecido com número/booleano" em YAML está entre aspas;
- [ ] Um conjunto de amostras de validação para JSON → YAML → JSON está definido e coberto por testes;
- [ ] Quando comentários precisam ser preservados, usa-se a Document API em vez de uma ida e volta por objeto;
- [ ] A convenção de mapeamento XML está documentada e a equipe compartilha um único conjunto de parâmetros;
- [ ] DTDs e entidades externas estão desabilitados ao analisar XML não confiável;
- [ ] Contagens de bytes e de campos são comparadas antes e depois da conversão, e qualquer anomalia gera um alerta.
Perguntas frequentes
Como escolher a direção de conversão entre JSON, YAML e XML?
Prefira JSON como formato intermediário: analise YAML e XML em um modelo de dados primeiro e depois serialize a partir desse modelo em vez de reescrever texto. Use a Document API do YAML quando comentários precisarem sobreviver e fixe uma convenção de mapeamento XML antes de precisar de atributos ou namespaces.
Por que a conversão XML para JSON perde atributos e namespaces?
O JSON não tem conceito de atributos ou namespaces, então atributos XML, prefixos e URIs de namespace precisam ser dobrados em chaves comuns, normalmente com prefixo @ ou um nó #text. Quando as regras de mapeamento divergem, o mesmo XML produz JSON diferente e não pode fazer ida e volta.
Devo manter JSON, YAML e XML em sincronia bidirecional de longo prazo?
Não. Comentários, âncoras, atributos XML e namespaces são perdidos entre formatos, e a sincronia bidirecional vai acumulando diferenças. Escolha um formato de origem e trate os outros como exportações pontuais ou visões temporárias.