Los límites de la conversión entre JSON ↔ YAML y XML
En resumen: La afirmación de que JSON es un subconjunto de YAML solo garantiza una dirección, y XML no tiene ningún mapeo oficial a JSON: todo se apoya en convenciones.

En resumen: usa JSON como formato intermedio: analiza el origen en un modelo de datos y después serializa al destino; antes de convertir, decide si los comentarios, los atributos y espacios de nombres de XML y los tipos numéricos o booleanos deben sobrevivir.
Que una tubería de conversión sea usable no depende de lo bonita que se vea la salida, sino de si eres consciente de cuánto ha descartado.
1. JSON y YAML: contención en una sola dirección
La especificación YAML 1.2 lo dice claramente: JSON es un subconjunto de YAML. Es decir, cualquier JSON válido es YAML válido:
{"name": "json", "tags": ["a", "b"], "count": 3}
Este fragmento es a la vez JSON y estilo flow de YAML. Lo contrario no se cumple.
| Característica | JSON | YAML 1.2 |
|---|---|---|
| Comentarios | ✗ | ✓ |
| Anclas y alias | ✗ | ✓ |
Varios documentos (---) |
✗ | ✓ |
| Claves que no son cadenas | ✗ | ✓ (1: a, true: b) |
| Claves complejas | ✗ | ✓ (? [a, b]) |
| Sensible a la indentación | ✗ | ✓ (se rechaza la indentación con tabuladores) |
| Inferencia de tipos | explícita | implícita (0o17, 0x1F, .inf) |
| Legibilidad en una sola línea | buena | mala (el contenido largo depende de la sintaxis de plegado) |
1.1 La inferencia de tipos es la mayor trampa
YAML analiza algunos valores que parecen cadenas como otros tipos:
zip: 01234 # ¿cadena o entero octal? el esquema core de YAML 1.2 lo trata como cadena
version: 1.10 # 1.1 (un float); la semántica se pierde
country: NO # una cadena en YAML 1.2; booleano false en YAML 1.1
date: 2026-10-30 # algunas implementaciones lo analizan como objeto de fecha
El ejemplo de NO ha quemado a mucha gente: con un analizador YAML 1.1 (algunas bibliotecas antiguas), country: NO se convierte en false.
Contramedida: entrecomilla todos los identificadores que "parecen un número o un booleano", como en version: "1.10".
1.2 Anclas, alias y múltiples documentos
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Tras expandir las anclas y convertir a JSON, normalmente acabas con dos objetos de configuración duplicados, o con un array (los múltiples documentos deben convertirse en [{...}, {...}]).
De cualquier forma, las anclas no se restauran en la conversión inversa: esa pérdida es inevitable.
1.3 Conversión que preserva los comentarios
La mayoría de las bibliotecas simplemente descartan los comentarios durante la conversión. Cuando necesites conservarlos, usa la API de Document del paquete yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Configuración del servicio
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Configuración del servicio
// port: 3000
La prueba es sencilla: si este archivo va a ser editado por personas una y otra vez, no tomes el camino "analizar → objeto → serializar".
1.4 Cómo verificar la consistencia de ida y vuelta
/**
* Verifica la consistencia de ida y vuelta para JSON → YAML → JSON.
* @param sourceValue Valor JSON original
* @param stringify Función de serialización YAML
* @param parse Función de análisis YAML
* @returns Si los dos resultados son exactamente iguales
*/
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);
}
Ejecutarla contra datos reales vale más que leer la documentación diez veces. Asegúrate de que tu conjunto de muestras cubra: null, la cadena vacía, el array vacío, "00", "1e5", cero negativo, enteros mayores que 2^53 y cadenas largas que contengan saltos de línea.
2. JSON y XML: no existe un mapeo estándar
XML es bastante anterior a JSON y tiene capacidades estructurales mucho más potentes (atributos, espacios de nombres, contenido mixto, DTD). Entre los dos no hay ninguna especificación de mapeo oficial; todas las herramientas hacen lo mismo: elegir una convención e intentar no perder demasiado.
2.1 Convenciones habituales
| Convención | Representación | Ejemplo |
|---|---|---|
| Elemento → clave de objeto | Un elemento con el mismo nombre se convierte en un campo | <a>1</a> → {"a": "1"} |
Atributo → prefijo @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Texto → #text |
Cuando hay tanto atributo como texto | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Elemento repetido → array | Dos o más apariciones se convierten en un array | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Espacio de nombres → prefijo | 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 pérdidas inevitables
- Orden de los elementos: los objetos JSON no tienen orden, mientras que XML es una secuencia ordenada de elementos;
- Atributos frente a elementos hijos: tienen semánticas distintas en XML, pero ambos se convierten en campos simples en JSON;
- Contenido mixto: estructuras como
<p>你好 <b>世界</b> 再见</p>no pueden expresarse con un objeto simple; - Declaraciones de espacio de nombres:
xmlnses un concepto de ámbito; una vez aplanado en campos, el ámbito se pierde; - CDATA y entidades:
<![CDATA[...]]>y&no son más que texto en JSON.
2.3 El dilema de "un elemento o un array"
<list><item>1</item></list>
Con un único item, xml2js devuelve un objeto por defecto y solo devuelve un array cuando hay dos.
Como resultado, el código del frontend tiene que escribir Array.isArray(x) ? x : [x].
Contramedida: activa la opción "siempre array" al analizar, o declara el campo como tipo array según un schema.
import { parseStringPromise } from 'xml2js';
const parsed = await parseStringPromise(xmlText, {
explicitArray: true, // devuelve un array incluso para un solo elemento
trim: true,
explicitRoot: false,
});
2.4 Recordatorio de seguridad
Antes de analizar XML no confiable debes desactivar las entidades externas, de lo contrario puedes desencadenar XXE (lectura de archivos locales, sondeo de redes internas):
// En el ecosistema Node, las DTD y las entidades externas suelen desactivarse mediante opciones del analizador
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON es solo la primera capa de la conversión; convertir no elimina la superficie de ataque que trae XML.
3. Dónde encaja cada uno de los tres formatos
| Escenario | Formato recomendado | Motivo |
|---|---|---|
| API HTTP públicas | JSON | Admitido de forma nativa por los navegadores y todos los lenguajes |
| Configuración mantenida a mano | YAML | Comentarios, indentación, legibilidad |
| Documentos que necesitan un schema fuerte y espacios de nombres (SOAP, Office Open XML, SVG) | XML | Capacidades estructurales y ecosistema existente |
| Interacción con herramientas de operaciones (K8s, CI) | YAML | El valor por defecto del ecosistema |
| Persistencia, cachés, registros | JSON / JSONL | Universal y sin dependencias |
4. Notas de implementación
import { stringify as toYaml } from 'yaml';
/**
* Convierte un valor JSON a texto YAML sin escapar los caracteres chinos.
* @param value El valor JSON que se va a convertir
* @returns El texto YAML
*/
function jsonToYaml(value: unknown): string {
return toYaml(value, {
indent: 2,
lineWidth: 0, // desactiva el ajuste automático para que las cadenas largas no se dividan
defaultStringType: 'QUOTE_DOUBLE',
});
}
Algunas opciones prácticas:
lineWidth: 0desactiva el ajuste automático; de lo contrario, las URL largas se parten entre líneas y se vuelven difíciles de leer;- entrecomillar todo con comillas dobles evita que valores como
NOy1.10se malinterpreten; - para datos con precisión numérica sensible, convierte a cadenas antes de serializar.
Para la dirección de XML, fija una única función de mapeo y pon el "prefijo de atributo", el "nombre de la clave de texto" y la "estrategia de arrays" en su firma en lugar de dispersarlos:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Lista de comprobación
- [ ] Todo identificador "parecido a número/booleano" en YAML está entrecomillado;
- [ ] Se ha definido un conjunto de muestras de validación para JSON → YAML → JSON y está cubierto por tests;
- [ ] Cuando hay que preservar comentarios, se usa la API de Document en lugar de una ida y vuelta por objeto;
- [ ] La convención de mapeo XML está escrita y el equipo comparte un único conjunto de parámetros;
- [ ] Las DTD y las entidades externas están desactivadas al analizar XML no confiable;
- [ ] Se comparan recuentos de bytes y de campos antes y después de la conversión, y cualquier anomalía genera una alerta.
Preguntas frecuentes
¿Cómo debo elegir la dirección de conversión entre JSON, YAML y XML?
Prefiere JSON como formato intermedio: analiza primero YAML y XML en un modelo de datos y después serializa desde ese modelo en lugar de reescribir el texto. Usa la API de Document de YAML cuando los comentarios deban sobrevivir, y fija una convención de mapeo XML antes de necesitar atributos o espacios de nombres.
¿Por qué la conversión de XML a JSON pierde atributos y espacios de nombres?
JSON no tiene el concepto de atributos ni de espacios de nombres, así que los atributos, prefijos y URI de espacio de nombres de XML deben plegarse en claves ordinarias, normalmente con un prefijo @ o un nodo #text. En cuanto las reglas de mapeo divergen, el mismo XML produce JSON distintos y no puede ir y volver.
¿Debo mantener JSON, YAML y XML sincronizados de forma bidireccional a largo plazo?
No. Los comentarios, las anclas, los atributos de XML y los espacios de nombres tienen pérdidas entre formatos, y la sincronización bidireccional va acumulando diferencias. Elige un formato de origen y trata los demás como exportaciones puntuales o vistas temporales.