ProgramandooIntentándolo

Convertir XML a JSON con javaScript

Desde el punto de vista del intercambio de datos JSON es una alternativa mejor al XML o al menos para la mayoria de los casos ya que un JSON es más legible, su conversión es mas simple (sobre todo en javaScript que no hay que hacer nada) (JSON, acrónimo de JavaScript Object Notation)) y también ocupa menos espacio.

A pesar de todo sigue habiendo servicios web antiguos que usan XML y como es un proceso tedioso usarlo sobretodo cuando sabes que con JSON no hay que hacer nada, pues mejor hacer una vez una función que haga el trabajo sucio y convierta el xml a json.

Y la función es esta:


/**
 * Convierte un xml a un objeto javaScript
 *
 * @param xml
 * @returns Objeto javaScipt equivalente al xml
 */
function convertirXmlEnObjeto(xml) {

    var objeto = {};
    var esRaiz = false;

    //  Objeto "raiz"
    if (xml.nodeType == 1) { // Objeto 
        // Se recuperan sus atributos
        if (xml.attributes.length > 0) {
            for (var j = 0; j < xml.attributes.length; j++) {
                var atributo = xml.attributes.item(j);
                objeto[atributo.nodeName] = atributo.nodeValue;
            }
        }
    } else if (xml.nodeType == 3) { // Texto
        objeto = xml.nodeValue;
    } else if (xml.nodeType == 9) { // Elemento raiz
        esRaiz = true;
    }

    // Atributos del objeto (objetos hijos)
    if (xml.hasChildNodes()) {
        for(var i = 0; i < xml.childNodes.length; i++) {
            var item = xml.childNodes.item(i);
            var nombreNodo = item.nodeName;

            // Si objeto no tiene un atributo con el nombre nombreNodo se anade, si ya lo tiene (es un array) se anade
            // a la lista del objeto con nombre nombreNodo
            if (typeof(objeto[nombreNodo]) == "undefined") {
                // Si el elemento es un CDATA se copia el contenido en el elemento y no se crea un
                // hijo para almacenar el texto
                if (nombreNodo == "#cdata-section") {
                    objeto = item.nodeValue;
                } else if (nombreNodo == "#text") { // Si el elemento es un texto no se crea el objeto #text
                    if (item.childNodes.length < 1) {
                        objeto = item.nodeValue;
                    } else {
                        objeto[nombreNodo] = convertirXmlEnObjeto(item);
                    }
                } else {
                    if (esRaiz) {
                        objeto = convertirXmlEnObjeto(item);
                    } else {
                        objeto[nombreNodo] = convertirXmlEnObjeto(item);
                    }
                }
            } else {
                // Si el atributo no es una lista se convierte el atributo en un array y se anade el
                // valor a dicho array
                if (typeof(objeto[nombreNodo].push) == "undefined") {
                    var valorAtributo = objeto[nombreNodo];
                    objeto[nombreNodo] = new Array();
                    objeto[nombreNodo].push(valorAtributo);
                }

                objeto[nombreNodo].push(convertirXmlEnObjeto(item));
            }
        }
    }
    
    return objeto;
}

Poco más de 50 lineas para convertir una respuesta XML en un objeto javaScript o lo que viene a ser lo mismo en un JSON.

Pese a ser una función recursiva y la complejidadad que eso proboca con los comentarios no deberia de ser dificil de entender, pero vamos con una pequeña explicación:

El primer paso es comprobar el tipo de nodo del elemento padre del xml en cada momento y basicamente lo que se hace es convertir sus atributos en atributos del objeto si es un nodo de tipo ELEMENT_NODE (1), asignar el valor al objeto si es un texto (un elemento final de un xml) o marcar si es el elemento raiz del documento xml para posteriormente no crear un objeto con ese nombre.

Tanto el tratamiento del elemento raiz como el de los atributos es por gusto personal porque me parece innecesario tener un objeto con solo un objeto dentro porque solo puede llevar a confusión y en lo referente a los atributos por simplicidad del objeto resultante final convertirlos directamente en objetos es una opción valida teniendo en cuenta que estamos perdiendo la diferenciación entre atributos y nodos hijos, mantener esta diferenciación sería tan simple como añadirle alguna marca a dichos atributos (ej. añadir algun prefijo como _) o crear un objeto atributos o algo similar y meterlos dentro de dicho objeto.

El segundo paso es tratar los elementos hijos del nodo raiz en cada momento haciendo basicamente la comprobación de si es un objeto o es un array (si hay varios nodos con el mismo nombre es un array) y desde cada hijo se recorre recursivamnete el xml para hacer la conversión completa.

Y para terminar un ejemplo de xml y del json/objeto javaScript resultante:


<coche marca="Chevrolet" modelo="Aveo" color="Blanco">
    <ruedas>
        <rueda eje="delantero" lado="derecha">
            <estado>Ok</estado>
        </rueda>
        <rueda eje="delantero" lado="izquierda">
            <estado>Ok</estado>
        </rueda>
        <rueda eje="trasero" lado="derecha">
            <estado>Ok</estado>
        </rueda>
        <rueda eje="trasero" lado="izquierda">
            <estado>Ok</estado>
        </rueda>
    </ruedas>
    <motor>
        <cc>1.3</cc>
        <tipo>Diesel</tipo>
    </motor>
</coche>

Para hacer la prueba sin tener un xml de “verdad” podemos usar la función parseXML de jQuery para crear un xml a partir de una cadena de texto.


var xml = '<coche marca="Chevrolet" modelo="Aveo" color="Blanco"><ruedas><rueda eje="delantero" lado="derecha"><estado>Ok</estado></rueda><rueda eje="delantero" lado="izquierda"><estado>Ok</estado></rueda><rueda eje="trasero" lado="derecha"><estado>Ok</estado></rueda><rueda eje="trasero" lado="izquierda"><estado>Ok</estado></rueda></ruedas><motor><cc>1.3</cc><tipo>Diesel</tipo></motor></coche>';

var coche = convertirXmlEnObjeto($.parseXML(xml));

// El resultado es este
coche = { 
    marca: "Chevrolet",
    modelo: "Aveo",
    color: "Blanco",
    ruedas: [
        {
            eje: "delantero",
            lado: "derecha",
            estado: "Ok"
        },
        {
            eje: "delantero", 
            lado: "izquierda", 
            estado: "Ok" },
        {
            eje: "trasero", 
            lado: "derecha", 
            estado: "Ok" },
        {
            eje: "trasero", 
            lado: "izquierda", 
            estado: "Ok" 
        }
    ],
    motor: 
        cc: "1.3",
        tipo: "Diesel"
    }
};