Tuesday, May 27, 2008

Super Simple XML-String to JSON

Yes, there are other libraries for this. Yes, they are more complete. So why this? I didn't need all the features of a complete xml->json map, I just wanted something to handle my nice, super simple use-case. And I hate wasted bandwidth. This library is 620 BYTES when YUI-compressed.

This is for a specific task. I trust the xml source. (So nobody yell "hey! - you're using eval() - you're eveel!").

  • you have a string of well-formed xml.
  • you don't have & don't care about node attributes, namespaces or CDATA (ie. your XML is 'Super Simple').

Good Ideas:
  • Don't use a library that will dirty the object prototype.
    (ie. mess up for-loops that look like this: for(var prop in obj){..})
  • Strip comments and whitespace, use safe variable-name substitution—use YUI Compressor.

  • It uses eval().
  • It uses regular-expressions (regexes).
  • It could probably stand to be optimized a bit.

The Code:

var xmlString = "<root><a/><b>data1</b><b>data2</b></root>";

function xmlString2json (xmlString) {
    return xmlString.replace(
            //expand empty tags
            /\<([^>]*)\/\>/g, "<$1></$1>"
            //convert closing tags to closing braces
            //convert opening tags to "{NODENAME:["
            //notice that the array literal is opened, but not closed.
            //remove extraneous commas
            //close array literal, begin adding needed singlequotes
            //adjust singlequotes, bring inside array literal notation (left side)
            //adjust singlequotes, bring inside array literal notation (right side)
            //remove empty strings (which are from empty tags)
            //remove final, ending extraneous comma
            /},$/, '}'

var json = xmlString2json(xmlString);
// {'root':[{'a':[]},{'b':['data1']},{'b':['data2']}]}

function cleanTree(r, p){
    if(r.length == 1 && typeof r[0] == "string"){
        //might want to trim leading and trailing whitespace from r[0] first
        // could be expressed as a series of ternary operations:
        // return r[0]=="true"?true:r[0]=="false"?false:r[0].search(/^[0-9]+$/)==0?1*r[0]:r[0]
        // expressed this way for readability:
        if(r[0] == "true"){
            return true;
        if(r[0] == "false"){
            return false;
        if(r[0].search(/^[0-9]+$/) == 0){
            return 1*r[0];
        return r[0];
    p = p || {};
    for( i in r ){
        var nn = '';
        for( name in r[i] ){
            nn = name;
        var subnode = r[i][nn];
            if(typeof p[nn] == "object" && typeof p[nn].length == "number"){
                p[nn].push( cleanTree(subnode) );
            } else {
                p[nn] = [ p[nn], cleanTree(subnode) ];
        } else {
            p[nn] = cleanTree(subnode);
    return p;

var jso = eval('('+json+')');
var cleaned = cleanTree(jso.testImages);

At this point, you might be wondering, "Why all those [extra] arrays?" My idea was to approximate the concept of the childNodes array. Really, you could just take the initial JSON and run with it, keeping in mind that it is structured with 'childNodes' arrays. But in case you'd rather have it 'cleaned' up .. I made the recursive cleanTree function to do just that.

"What if I have actual xml, not just a string representation thereof?" you may question.

Give this a shot:

var xmlString = (new XMLSerializer()).serializeToString( myXMLDoc );

Someone else's related post (reminder to self to see if his xml2json will work in actionscript / how readily adaptable it is): converting xml to json.