Convert XML to JSON with JavaScript

If you follow me on Twitter, you know that I've been working on a super top secret mobile application using Appcelerator Titanium.  The experience has been great:  using JavaScript to create easy to write, easy to test, native mobile apps has been fun.  My mobile app connects to numerous social network APIs, some of which only provide an XML response.  My mini "framework" uses JSON to dynamically create widgets so I've needed a way to turn XML into JSON.  I found many solutions but none of them worked.  After tweaking an existing function, I've found a solution that works great.

The JavaScript

It's important to point out that Titanium's Titanium.XML.DOMDocument object implements DOM2-level structures.  Here's the magic XML to JSON code:

// Changes XML to JSON
function xmlToJson(xml) {
	
	// Create the return object
	var obj = {};

	if (xml.nodeType == 1) { // element
		// do attributes
		if (xml.attributes.length > 0) {
		obj["@attributes"] = {};
			for (var j = 0; j < xml.attributes.length; j++) {
				var attribute = xml.attributes.item(j);
				obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
			}
		}
	} else if (xml.nodeType == 3) { // text
		obj = xml.nodeValue;
	}

	// do children
	if (xml.hasChildNodes()) {
		for(var i = 0; i < xml.childNodes.length; i++) {
			var item = xml.childNodes.item(i);
			var nodeName = item.nodeName;
			if (typeof(obj[nodeName]) == "undefined") {
				obj[nodeName] = xmlToJson(item);
			} else {
				if (typeof(obj[nodeName].length) == "undefined") {
					var old = obj[nodeName];
					obj[nodeName] = [];
					obj[nodeName].push(old);
				}
				obj[nodeName].push(xmlToJson(item));
			}
		}
	}
	return obj;
};

The major change I needed to implement was using attributes.item(j) instead of the attributes[j] that most of the scripts I found used.  With this function, XML that looks like:

<ALEXA VER="0.9" URL="davidwalsh.name/" HOME="0" AID="=">
	<SD TITLE="A" FLAGS="" HOST="davidwalsh.name">
		<TITLE TEXT="David Walsh Blog :: PHP, MySQL, CSS, Javascript, MooTools, and Everything Else"/>
		<LINKSIN NUM="1102"/>
		<SPEED TEXT="1421" PCT="51"/>
	</SD>
	<SD>
		<POPULARITY URL="davidwalsh.name/" TEXT="7131"/>
		<REACH RANK="5952"/>
		<RANK DELTA="-1648"/>
	</SD>
</ALEXA>

...becomes workable a JavaScript object with the following structure:

{
	"@attributes": {
		AID: "=",
		HOME:  0,
		URL: "davidwalsh.name/",
		VER: "0.9",
	},
	SD = [
		{
			"@attributes": {
				FLAGS: "",
				HOST: "davidwalsh.name",
				TITLE: A
			},
			LINKSIN: {
				"@attributes": {
					NUM: 1102
				}
			},
			SPEED: {
				"@attributes": {
					PCT: 51,
					TEXT: 1421
				}
			},
			TITLE: {
				"@attributes": {
					TEXT: "David Walsh Blog :: PHP, MySQL, CSS, Javascript, MooTools, and Everything Else",
				}
			},
		},
		{
			POPULARITY: {
				"@attributes": {
					TEXT: 7131,
					URL: "davidwalsh.name/"
				}
			},
			RANK: {
				"@attributes": {
					DELTA: "-1648"
				}
			},
			REACH: {
				"@attributes": {
					RANK = 5952
				}
			}
		}
	]
}

From here you can use the JavaScript object however you see fit. If you'd like the JavaScript in string JSON format, you can code:

// Assuming xmlDoc is the XML DOM Document
var jsonText = JSON.stringify(xmlToJson(xmlDoc));

This function has been extremely useful in allowing me to quickly disregard XML and use JSON instead.  The function works well when structuring attributes and arrays of nested child nodes.  Keep this handy;  at some point you may need to convert XML to JSON!


Comments

  1. Giacomo Balli

    Nicely put. Good work!

  2. Bruno Gama

    Pretty good topic, i am working in a ios project that needed localization and all kinda of stuffs and coredata was a pain in the ass to make it work, so i have made a xml file for each language and for my surprise, nsxmlparser is a sax parser and it really sucks, so i converted my xml file in a json with this site: http://shlang.com/xml2json/

    it worked and the json validated in the jsonlint.com

  3. John-David Dalton

    Your code example has several incorrect references to $xmlToJson.

    I took a stab at cleaning up the xmlToJson function here.

  4. Rob james

    Is there any reason you’re not using the YQL function in titanium to do this? I’ve found it worked a treat, only I’m not sure how efficient it is.

    • David Walsh

      My understanding is that I would need to then connect to Yahoo!’s servers. I’d prefer not to do that. They could change their service API, ban me, etc. Cutting YQL out cuts out a possible problem.

    • Rob James

      Yeah, probably the right decision based on those criteria. Though I had a tiny inkling that the YQL library was built in, it’s probably not, but is sure is fast!

      Cool, keep up the good work!

  5. Christian Harms

    Yes, JSON format need the quotes. Only keys of Objects in JavaScript can leave the quotating. It looks similar but dont mix it.

  6. keif

    Manually replacement? C’mon, regex replacement, live in the now! ;)

  7. Simon

    You’re a rock star!

  8. Matt Bridges

    Does item.nodeName include namespaces? I’m not sure what you would do in the case of namespace conflicts when converting to JSON, short of naming conventions.

  9. Frank van Puffelen

    You may want to change the line that checks if the JS object is already an array from:

    if (typeof(obj[nodeName].length) == "undefined") {

    to:

    if (typeof(obj[nodeName].push) == "undefined") {

    As it currently is, the code will fail (on Chrome 10 at least) on XML that has whitespace nodes between the elements. The check for length will result in a false positive “yes, this #text item is an array”.

    With my modification the code will be able to handle XML document with whitespace nodes. It’ll still output lots of #text items in the arrays though and you’ll lose the order of the text nodes vs. sub-elements. YMMV.

  10. HTMLChess

    I have perfected your work… :-)

    ciao

    // a normal XMLHttpRequest function: it retrieves a file via XMLHTTPRequest, then calls fncCallback when done.

    function XHR(sURL, fncCallback /*, argumentToPass1, argumentToPass2, etc. */) {

    var oResp, aArgs = Array.prototype.slice.call(arguments, 2);

    if (window.XMLHttpRequest) { oResp = new XMLHttpRequest(); }

    else if (window.ActiveXObject) { oResp = new ActiveXObject("Microsoft.XMLHTTP"); }

    if (oResp) {

    if (fncCallback) {

    if (typeof oResp.onload !== "undefined")

    oResp.onload = function() {

    fncCallback.apply(oResp, aArgs);

    oResp = null;

    };

    else {

    oResp.onreadystatechange = function() {

    if (oResp.readyState === 4) {

    fncCallback.apply(oResp, aArgs);

    oResp = null;

    }

    };

    }

    }

    oResp.open("GET", sURL, true);

    oResp.setRequestHeader("Content-Type", "text/plain");

    oResp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");

    oResp.send(null);

    }

    }

    // recursive function which converts an XML DOM to a JavaScript Object
    function xml2Obj (oXMLDom) {
    var oRObj = true;
    if (oXMLDom.nodeType === 3) { // text
    oRObj = oXMLDom.nodeValue.replace(/^\s+|\s+$/g, "");
    } else {
    if (oXMLDom.nodeType === 1) { // element
    // do attributes
    if (oXMLDom.attributes.length > 0) {
    var iAttrib;
    oRObj = {};
    oRObj["@attributes"] = {};
    for (var iAttrId = 0; iAttrId < oXMLDom.attributes.length; iAttrId++) {
    iAttrib = oXMLDom.attributes.item(iAttrId);
    oRObj["@attributes"][iAttrib.nodeName] = iAttrib.nodeValue;
    }
    }
    }
    // do children
    if (oXMLDom.hasChildNodes()) {
    var iKey, iValue, iXMLNode;
    if (oRObj === true) { oRObj = {}; }
    for (var iChildId = 0; iChildId < oXMLDom.childNodes.length; iChildId++) {
    iXMLNode = oXMLDom.childNodes.item(iChildId);
    iKey = iXMLNode.nodeType === 3 ? "@content" : iXMLNode.nodeName;
    iValue = xml2Obj(iXMLNode);
    if (oRObj.hasOwnProperty(iKey)) {
    if (iXMLNode.nodeType === 3) { oRObj[iKey] += iValue; }
    else {
    if (oRObj[iKey].constructor !== Array) { oRObj[iKey] = [oRObj[iKey]]; }
    oRObj[iKey].push(iValue);
    }
    } else if (iXMLNode.nodeType !== 3 || iValue !== "") { oRObj[iKey] = iValue; }
    }
    }
    }
    return(oRObj);
    };

    // function called via ajax callback
    function myFunction() {
    // gets the object
    var oMyObject = xml2Obj(this.responseXML);

    // converts the object to a string and display it in an alert message
    alert(JSON.stringify(oMyObject));
    }

    Click me!

  11. HTMLChess

    sorry…


    <!doctype html>
    <html>
    <head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
    <title>XML to JavaScript Object conversion example</title>
    <script type="text/javascript">
    /**
    * a normal XMLHttpRequest function: it retrieves a file via XMLHTTPRequest, then calls the "callbackFnc" function when done.
    **/
    function XHR(sURL, callbackFnc /*, argumentToPass1, argumentToPass2, etc. */) {
        var oResp, aArgs = Array.prototype.slice.call(arguments, 2);
        if (window.XMLHttpRequest) { oResp = new XMLHttpRequest(); }
        else if (window.ActiveXObject) { oResp = new ActiveXObject("Microsoft.XMLHTTP"); }
        if (oResp) {
            if (callbackFnc) {
                if (typeof oResp.onload !== "undefined")
                    oResp.onload = function() {
                        callbackFnc.apply(oResp, aArgs);
                        oResp = null;
                    };
                else {
                    oResp.onreadystatechange = function() {
                        if (oResp.readyState === 4) {
                            callbackFnc.apply(oResp, aArgs);
                            oResp = null;
                        }
                    };
                }
            }
            oResp.open("GET", sURL, true);
              oResp.setRequestHeader("Content-Type", "text/plain");
            oResp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
            oResp.send(null);
        }
    }

    /**
    * the recursive function which converts an XML Document to a JavaScript Object;
    * it will consider only the following node types and their attributes:
    * - Document,
    * - Element,
    * - Text,
    * - CDATASection;
    * all other informations will be lost!
    * it is a conscious choice.
    * please, see http://www.w3schools.com/dom/dom_nodetype.asp
    **/
    function xml2Obj (oXMLNode) {
        // default value for empty elements; it could be replaced with "null" instead of "true"... but i prefer so, because the truth is what appears :-)
        var vResult = true;
        // node attributes
        if (oXMLNode.attributes && oXMLNode.attributes.length > 0) {
            var iAttrib;
            vResult = {};
            vResult["@attributes"] = {};
            for (var iAttrId = 0; iAttrId < oXMLNode.attributes.length; iAttrId++) {
                iAttrib = oXMLNode.attributes.item(iAttrId);
                vResult["@attributes"][iAttrib.nodeName] = iAttrib.nodeValue;
            }
        }
        // children
        if (oXMLNode.hasChildNodes()) {
            var iKey, iValue, iXMLChild;
            if (vResult === true) { vResult = {}; } // if above you have changed the default value, then it must be also replaced within this "if statement" in the same way...
            for (var iChild = 0; iChild < oXMLNode.childNodes.length; iChild++) {
                iXMLChild = oXMLNode.childNodes.item(iChild);
                if ((iXMLChild.nodeType & 7) === 1) { // nodeType is "Document" (9) or "Element" (1)
                    iKey = iXMLChild.nodeName;
                    iValue = xml2Obj(iXMLChild);
                    if (vResult.hasOwnProperty(iKey)) {
                        if (vResult[iKey].constructor !== Array) { vResult[iKey] = [vResult[iKey]]; }
                        vResult[iKey].push(iValue);
                    } else { vResult[iKey] = iValue; }
                } else if ((iXMLChild.nodeType - 1 | 1) === 3) { // nodeType is "Text" (3) or "CDATASection" (4)
                    iKey = "@content";
                    iValue = iXMLChild.nodeType === 3 ? iXMLChild.nodeValue.replace(/^\s+|\s+$/g, "") : iXMLChild.nodeValue;
                    if (vResult.hasOwnProperty(iKey)) { vResult[iKey] += iValue; }
                    else if (iXMLChild.nodeType === 4 || iValue !== "") { vResult[iKey] = iValue; }
                }
            }
        }
        return(vResult);
    }

    // function called via ajax callback
    function myFunction() {
        // converts the XML document got via XMLHttpRequest
        var oMyObject = xml2Obj(this.responseXML);

        // converts the resultant object to a string and displays it in a textarea
        document.outputForm.outputBox.value = JSON.stringify(oMyObject);
    }
    </script>
    </head>

    <body>
    <h1>&ldquo;XML Document&rdquo; to &ldquo;JavaScript Object&rdquo; conversion example</h1>
    <form name="outputForm">
    <p><textarea name="outputBox" style="width: 100%; height: 300px;"></textarea></p>
    <p style="text-align: center"><input type="button" name="convertBtn" onclick="XHR('example.xml', myFunction);" value="Click me!" style="font-size: 24px;" /></p>
    <p>&hellip;I'll try to load the &ldquo;<a href="example.xml" title="Open the example XML file in a new tab." target="_blank">example.xml</a>&rdquo; file.</p>
    </form>
    </body>
    </html>

  12. spock

    Cool…I’m looking to go the other way with a twist.

    I need a simple way to take JSON output & convert it to CSV,
    but haven’t been able to locate any business user friendly utils for
    that.

    Thoughts?

  13. Jeff

    Is there anyway to grab CDATA values in your script?

  14. Flash

    I have put together a little JSON sample that iterates over a JavaScript object and posts the property values to a cross domain server that is hosts by a DotNet.aspx page that then converts a C# object to a JSON string that is then posted back to the browser and converted back to a JavaScript object without having to use Window.Eval()

    The resultant JavaScript object is then finally past back to a call-back function that is ready to uses and the code does not need 3rd party libraries, works in net framework 2.0 and upwards and has been tested with IE6-IE9, Firefox plus it’s lightweight.

    Click my name for full details

  15. binaryfever

    Thanks! Just started a titanium where a service only returns xml. Just the thing im looking for.

  16. John

    Hi!

    I’m trying to test your function in a webservice response but I can’t execute it. Always that I’ll try to compile and execute with Titanium SDK 1.7.5 it said me:

    [ERROR] Script Error = Result of expression ‘xml.hasChildNodes’ [undefined] is not a function. at XMLTools.js (line 22).

    I’m introducing your code in a file called XMLTools.js.

    Thanks so much!

  17. James

    Awesome script! however, I’m getting the following Runtime Error:

    TypeError: Cannot find function hasChildNodes in object [Ti.NodeList]...

    Please advise.

    Thanks!

    • Jani Patokallio

      You’re not passing in a DOM element. If you’re using JQuery, the easiest thing is to call $(your_xml_string)[0] to convert it.


Be Heard!

Share your thoughts without being a jerk! And wrap your code in <code> tags, f00!

Name*:
Email*:
Website: