XML a JSON: la migración que todo backend legacy necesita hacer bien
Diferencias estructurales XML vs JSON, trampas de la conversión (atributos, namespaces, arrays vs objetos), casos reales de SOAP a REST.
El despertar del desarrollador: cuando SOAP se convierte en tu pesadilla
Llegué a mi trabajo actual hace tres años. El primer día, el CTO me dijo: "Tenemos un sistema SOAP legacy que necesitamos migrar a REST. Es simple, solo convierte XML a JSON". Sonó fácil. Pasé los siguientes seis meses luchando con conversiones que parecían triviales pero escondían trampas que rompían sistemas enteros.
El problema no era la tecnología en sí. XML y JSON son ambos formatos de intercambio de datos. El problema era que estaban diseñados con filosofías completamente diferentes. XML viene del mundo de los documentos (SGML, HTML), JSON viene del mundo de la programación (JavaScript objects). Cuando intentas traducir uno al otro sin entender estas diferencias, creas sistemas frágiles que fallan en los peores momentos posibles.
Este tutorial es lo que desearía haber leído ese primer día. No solo te diré cómo convertir XML a JSON, sino que te explicaré las trampas que he aprendido por las malas, para que no tengas que descubrirlas tú mismo.
XML vs JSON: dos mundos, dos filosofías
Para entender por qué la conversión es difícil, primero necesitas entender qué hace diferente a cada formato.
XML: el mundo de los documentos
XML fue diseñado en los años 90 como una forma de estructurar documentos. Su filosofía es "todo es un elemento con atributos":
<usuario id="123" activo="true">
<nombre>Juan García</nombre>
<email>juan@example.com</email>
<roles>
<rol>admin</rol>
<rol>editor</rol>
</roles>
</usuario>
Características clave de XML:
- Atributos vs elementos: Los datos pueden estar en atributos (
id="123") o en elementos (<nombre>) - Namespaces: Los elementos pueden tener prefijos que indican su origen (
<soap:Envelope>) - Orden significativo: El orden de los elementos puede tener significado semántico
- Tipos explícitos: A través de schemas (XSD) puedes declarar tipos
- Comentarios y metadatos: XML soporta comentarios, CDATA, processing instructions
JSON: el mundo de los objetos
JSON nació en 2000 como un subconjunto de JavaScript. Su filosofía es "todo es un objeto o array":
{
"id": 123,
"activo": true,
"nombre": "Juan García",
"email": "juan@example.com",
"roles": ["admin", "editor"]
}
Características clave de JSON:
- Sin atributos: Todo es un par clave-valor
- Sin namespaces: Solo nombres simples
- Orden no significativo: Por especificación, el orden de las claves no importa
- Tipos implícitos: Los tipos se infieren del valor (string, number, boolean, array, object)
- Sin comentarios: JSON puro no soporta comentarios
Estas diferencias son la raíz de todos los problemas de conversión.
Trampa #1: Atributos vs elementos — ¿dónde van los datos?
Esta es la trampa más común. En XML, los datos pueden estar en atributos o elementos. En JSON, todo es un par clave-valor. ¿Cómo decides?
El problema
<!-- XML original -->
<usuario id="123" activo="true">
<nombre>Juan García</nombre>
</usuario>
¿Debería convertirse a esto?
// Opción A: atributos como campos normales
{
"id": 123,
"activo": true,
"nombre": "Juan García"
}
¿O a esto?
// Opción B: atributos en un objeto separado
{
"attributes": {
"id": 123,
"activo": true
},
"nombre": "Juan García"
}
La solución práctica
En la mayoría de casos, la Opción A es la correcta. Los atributos XML generalmente representan metadatos del elemento (ID, flags), y en JSON esos metadatos son simplemente campos del objeto.
Sin embargo, hay excepciones importantes:
Casos donde usar Opción B (atributos separados):
- Cuando hay conflicto de nombres: Si un elemento tiene un atributo y un subelemento con el mismo nombre
<usuario nombre="Juan">
<nombre>Juan García</nombre>
</usuario>
Aquí necesitas separar para no perder información:
{
"attributes": {
"nombre": "Juan"
},
"nombre": "Juan García"
}
- Cuando necesitas preservar la estructura XML: Si tu sistema downstream necesita saber qué era atributo y qué era elemento
Nuestra herramienta XML a JSON usa la Opción A por defecto, pero te permite configurar el comportamiento según tus necesidades.
Trampa #2: Arrays vs objetos — el problema de la cardinalidad
Esta es la trampa que más sistemas ha roto en producción. En XML, un elemento puede aparecer una vez o múltiples veces. En JSON, tienes que decidir explícitamente si algo es un array o un objeto.
El problema
<!-- XML con un solo rol -->
<usuario>
<roles>
<rol>admin</rol>
</roles>
</usuario>
¿Debería convertirse a esto?
// Opción A: array aunque tenga un elemento
{
"roles": ["admin"]
}
¿O a esto?
// Opción B: objeto si tiene un elemento
{
"roles": "admin"
}
Ahora imagina que mañana el usuario tiene dos roles:
<!-- XML con dos roles -->
<usuario>
<roles>
<rol>admin</rol>
<rol>editor</rol>
</roles>
</usuario>
Si usaste la Opción B, tu conversión ahora falla porque roles ya no es un string, es un array. Tu código downstream que esperaba roles como string ahora rompe.
La solución práctica
Regla de oro: Si un elemento puede aparecer múltiples veces, siempre conviértelo a array, incluso si actualmente solo aparece una vez.
// Siempre array, sin importar el número de elementos
{
"roles": ["admin"] // Un elemento → array de uno
}
{
"roles": ["admin", "editor"] // Dos elementos → array de dos
}
Esto hace que tu API sea consistente. El cliente siempre puede tratar roles como array, sin importar cuántos roles haya.
Excepción: Si estás 100% seguro de que un elemento nunca aparecerá más de una vez (por ejemplo, <nombre>), puedes usar un string. Pero ten cuidado: las suposiciones de "nunca" suelen romperse.
Trampa #3: Namespaces — el prefijo que no desaparece
XML usa namespaces para evitar conflictos de nombres. JSON no tiene equivalente. ¿Qué haces con los prefijos?
El problema
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetUser xmlns:m="http://example.com/api">
<m:id>123</m:id>
</m:GetUser>
</soap:Body>
</soap:Envelope>
¿Debería convertirse a esto?
// Opción A: mantener prefijos
{
"soap:Envelope": {
"soap:Body": {
"m:GetUser": {
"m:id": 123
}
}
}
}
¿O a esto?
// Opción B: eliminar prefijos
{
"Envelope": {
"Body": {
"GetUser": {
"id": 123
}
}
}
}
La solución práctica
Depende de tu caso de uso:
Para APIs SOAP → REST: Generalmente quieres eliminar los prefijos. Los namespaces SOAP son metadatos de protocolo, no datos de negocio. Tu API REST debería ser limpia:
{
"user": {
"id": 123
}
}
Para conversión genérica: Si estás construyendo una herramienta de conversión genérica (como nuestra herramienta XML a JSON), mantén los prefijos o crea un campo separado para namespaces:
{
"Envelope": {
"_xmlns": {
"soap": "http://schemas.xmlsoap.org/soap/envelope/"
},
"Body": {
"GetUser": {
"_xmlns": {
"m": "http://example.com/api"
},
"id": 123
}
}
}
}
Esto preserva la información de namespaces sin ensuciar los nombres de los campos.
Trampa #4: Tipos de datos — string vs number vs boolean
XML no tiene tipos nativos. Todo es texto. Los tipos se definen a través de schemas (XSD) o se infieren del contexto. JSON tiene tipos explícitos.
El problema
<usuario>
<id>123</id>
<activo>true</activo>
<saldo>100.50</saldo>
</usuario>
¿Debería convertirse a esto?
// Opción A: todo string (como XML)
{
"id": "123",
"activo": "true",
"saldo": "100.50"
}
¿O a esto?
// Opción B: inferir tipos
{
"id": 123,
"activo": true,
"saldo": 100.50
}
La solución práctica
Inferir tipos siempre que sea posible. JSON con tipos correctos es más útil para los consumidores:
{
"id": 123, // number → útil para cálculos
"activo": true, // boolean → útil para condiciones
"saldo": 100.50 // number → útil para cálculos
}
Sin embargo, hay casos donde debes mantener todo como string:
- Cuando no tienes un schema XSD: Sin schema, la inferencia es una suposición.
123podría ser un ZIP code (string) o un ID (number). - Cuando el valor puede tener ceros a la izquierda:
00123como number se convierte en123. - Cuando el valor es un número muy grande: JSON tiene limitaciones en la precisión de números grandes.
Nuestra herramienta XML a JSON intenta inferir tipos, pero te permite desactivar esta inferencia si necesitas preservar todo como string.
Trampa #5: Orden de elementos — ¿importa?
En XML, el orden de los elementos puede tener significado. En JSON, por especificación, el orden de las claves de un objeto no debería importar.
El problema
<transaccion>
<paso1>Validar</paso1>
<paso2>Procesar</paso2>
<paso3>Confirmar</paso3>
</transaccion>
Si conviertes a JSON:
{
"paso1": "Validar",
"paso2": "Procesar",
"paso3": "Confirmar"
}
El orden de las claves en el objeto JSON no está garantizado. Un parser podría reordenarlas. Si tu sistema depende del orden, esto rompe.
La solución práctica
Si el orden importa, usa arrays:
{
"pasos": [
{ "nombre": "paso1", "valor": "Validar" },
{ "nombre": "paso2", "valor": "Procesar" },
{ "nombre": "paso3", "valor": "Confirmar" }
]
}
Los arrays en JSON sí preservan orden. Esta es la forma correcta de representar datos secuenciales en JSON.
Implementación práctica: conversión en Node.js
Aquí está un patrón que uso para conversiones robustas:
const xml2js = require('xml2js');
function xmlToJson(xmlString, options = {}) {
const parser = new xml2js.Parser({
explicitArray: false, // No crear arrays para elementos únicos
mergeAttrs: true, // Fusionar atributos con elementos
explicitCharkey: false, // No usar campo especial para texto
trim: true, // Eliminar espacios en blanco
...options
});
return new Promise((resolve, reject) => {
parser.parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
// Uso básico
async function convertXML() {
const xml = `
<usuario id="123">
<nombre>Juan</nombre>
<roles>
<rol>admin</rol>
<rol>editor</rol>
</roles>
</usuario>
`;
const json = await xmlToJson(xml);
console.log(JSON.stringify(json, null, 2));
}
// Resultado:
// {
// "usuario": {
// "id": "123",
// "nombre": "Juan",
// "roles": {
// "rol": ["admin", "editor"]
// }
// }
// }
Conversión con inferencia de tipos
function inferTypes(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(inferTypes);
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
// Intentar convertir a number
if (typeof value === 'string' && !isNaN(Number(value))) {
// Verificar que no sea un string que parece número pero no lo es
if (!value.startsWith('0') || value === '0') {
result[key] = Number(value);
continue;
}
}
// Intentar convertir a boolean
if (value === 'true') {
result[key] = true;
continue;
}
if (value === 'false') {
result[key] = false;
continue;
}
// Recursión para objetos anidados
result[key] = inferTypes(value);
}
return result;
}
// Uso
const json = await xmlToJson(xml);
const typedJson = inferTypes(json);
Caso real: migración de SOAP a REST
Aquí está un ejemplo real de una migración que hice. El sistema original tenía este SOAP:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns:GetOrder xmlns:ns="http://example.com/orders">
<ns:orderId>ORD-12345</ns:orderId>
</ns:GetOrder>
</soap:Body>
</soap:Envelope>
Respuesta SOAP:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns:GetOrderResponse xmlns:ns="http://example.com/orders">
<ns:order id="ORD-12345" status="pending">
<ns:customer id="CUST-001">
<ns:name>Juan García</ns:name>
<ns:email>juan@example.com</ns:email>
</ns:customer>
<ns:items>
<ns:item id="ITEM-001" quantity="2" price="29.99">
<ns:name>Producto A</ns:name>
</ns:item>
<ns:item id="ITEM-002" quantity="1" price="49.99">
<ns:name>Producto B</ns:name>
</ns:item>
</ns:items>
<ns:total>109.97</ns:total>
</ns:order>
</ns:GetOrderResponse>
</soap:Body>
</soap:Envelope>
API REST equivalente:
// GET /api/orders/ORD-12345
{
"id": "ORD-12345",
"status": "pending",
"customer": {
"id": "CUST-001",
"name": "Juan García",
"email": "juan@example.com"
},
"items": [
{
"id": "ITEM-001",
"quantity": 2,
"price": 29.99,
"name": "Producto A"
},
{
"id": "ITEM-002",
"quantity": 1,
"price": 49.99,
"name": "Producto B"
}
],
"total": 109.97
}
Observa las decisiones:
- Eliminé namespaces (SOAP es metadato de protocolo)
- Atributos (
id,status,quantity,price) se convirtieron a campos normales <items>con múltiples<item>se convirtió a array- Tipos inferidos (
quantity,price,totalcomo numbers) - Estructura simplificada (sin
Envelope,Body,Response)
Herramientas recomendadas
Para Node.js
xml2js: La librería más popular. Flexible pero requiere configuración cuidadosa.
npm install xml2js
fast-xml-parser: Más rápida que xml2js, mejor inferencia de tipos.
npm install fast-xml-parser
Para el navegador
Nuestra herramienta XML a JSON funciona completamente en el navegador, sin enviar datos a ningún servidor. Ideal para pruebas rápidas.
Para línea de comandos
# Usando jq (si tienes XML convertido a JSON preliminar)
cat response.xml | xml2json | jq '.'
Checklist para migración exitosa
Antes de migrar tu sistema SOAP/XML a REST/JSON:
☐ Define tu estrategia de arrays
- ¿Qué elementos siempre serán arrays aunque tengan un solo item?
- Documenta esta decisión para los consumidores de tu API
☐ Define tu estrategia de atributos
- ¿Fusionar atributos con elementos o mantenerlos separados?
- ¿Cómo manejar conflictos de nombres?
☐ Define tu estrategia de namespaces
- ¿Eliminar prefijos SOAP o preservarlos?
- ¿Cómo manejar namespaces de negocio?
☐ Define tu estrategia de tipos
- ¿Inferir tipos o mantener todo como string?
- ¿Cómo manejar casos límite (números grandes, ceros a la izquierda)?
☐ Prueba con datos reales
- No solo con XML de ejemplo. Usa datos de producción.
- Los casos límite aparecen en datos reales, no en ejemplos.
☐ Versiona tu API
- La primera versión de tu API REST probablemente no será perfecta.
- Usa versionado (
/api/v1/orders) para poder iterar.
Conclusión: la conversión es una traducción, no un transpilado
Convertir XML a JSON no es como transpilar TypeScript a JavaScript. Es más como traducir un poema de español a inglés. No puedes traducir palabra por palabra; tienes que entender el contexto, la cultura, y las intenciones del autor original.
Las trampas que he descrito (atributos, arrays, namespaces, tipos, orden) no son bugs de las herramientas. Son diferencias fundamentales entre dos filosofías de diseño de datos. La mejor herramienta no es la que convierte automáticamente, sino la que te permite configurar la conversión según las necesidades de tu caso específico.
Nuestra herramienta XML a JSON está diseñada con esta filosofía: te da control sobre cada aspecto de la conversión, porque entendemos que cada migración es diferente.
La próxima vez que alguien te diga "solo convierte XML a JSON", ya sabes que no es tan simple. Pero con las estrategias correctas, es completamente manejable.
관련 기사
Renderizar Markdown de forma segura: XSS y sanitización
Por qué renderizar Markdown de usuarios sin sanitizar es un vector de XSS, cómo marked + sanitize-html mitigan el riesgo, buenas prácticas.
Unix Timestamps y Zonas Horarias: el bug que aparece solo en producción
Epoch time, UTC vs zona local, el problema del año 2038, errores comunes al comparar fechas en JS/SQL.