From Xml to JSON


In Convert JSON to XML I showed how to make XML from JSON. Here's the opposite. It could be a little more all encompassing by dealing with dates, and unescaping, but for now this should serve most simple needs.

There is now a webapp api of this too which you can use to convert online. See Using Apps Script for xml json conversion

How to use


Let's start with this XML string that needs to be converted to a JSON object.
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <aString>some string</aString>
  <aNumber>8</aNumber>
  <aBoolean>false</aBoolean>
  <anObject>
    <apis>
      <name>numbers</name>
      <url>http://numbersapi.com/</url>
    </apis>
    <apis>
      <name>faa</name>
      <url>http://services.faa.gov/airport/status</url>
    </apis>
  </anObject>
  <anArray>
    <value>8</value>
    <result>legs an arachnid has</result>
  </anArray>
  <anArray>
    <value>8</value>
    <result>furlongs in a mile</result>
  </anArray>
  <anotherArray>1</anotherArray>
  <anotherArray>2</anotherArray>
  <anotherArray>3</anotherArray>
</root>

It can be used like this
  var obFromXml =  makeObFromXml ( XmlService.parse (xmlString).getRootElement() );

The result

This is able to reverse the conversion done by the second approach used in Convert JSON to XML. Any XML attributes will be converted to regular key/value pairs, since the concept of an attribute doesn't exist in JSON. I did consider creating an attribute object, but in the end decided on this approach where a value and an attribute are considered the same. If an element has both attributes and a text node, the text node will be added to a property called text.
{
"aString": "some string",
"aNumber": 8,
"aBoolean": false,
"anObject": {
"apis": [{
"name": "numbers",
"url": "http://numbersapi.com/"
}, {
"name": "faa",
"url": "http://services.faa.gov/airport/status"
}]
},
"anArray": [{
"value": 8,
"result": "legs an arachnid has"
}, {
"value": 8,
"result": "furlongs in a mile"
}],
"anotherArray": [1, 2, 3]
}





The code

It's recursive, so in the end it's just a few lines of code. Note that it converts numbers and booleans from text in the fixValue function. This could easily be extended to convert dates or unescape strings between JSON and XML formats if necessary.
/**
* traverse an xml tree and create a js object
* @param {XmlElement} xmlElement the parent
* @return {object||string} a new branch
*/
function makeObFromXml(xmlElement) {
  
  // parent for this iteration
  var job = {};
  var name = xmlElement.getName();
  
  // attributes are converted to children
  xmlElement.getAttributes().forEach(function(d) {
    var child = XmlService.createElement(d.getName());
    child.setText(d.getValue());
    xmlElement.addContent(child);
  });
  
  
  // any children
  var kids = xmlElement.getChildren();
  if (!kids.length) {
    // its just a value
    return fixValue(xmlElement.getText());
  } 
  
  else {
    
    // if there are any children we need to recurse
    kids.forEach (function(d) {
      store ( job , d.getName() , makeObFromXml(d));
    });
    
    // if there is also a text node, we need to add that too, but also create a node for it
    store (job , 'text' ,  fixValue(xmlElement.getText()));
    
  }
  return job;
  
  function store( job , name , value ) {
    

    // if it's a repeated key, then we need to turn into an array
    if (job.hasOwnProperty(name) && !Array.isArray (job[name])) {
      job[name] = [job[name]];
    }
    
    // push or assign
    if (value !== '') {
      if (Array.isArray (job[name])) {
        job[name].push (value);
      }
      else {
        job[name] = value;
      }
    }
    
  }
  
  /**
   * converts strings from xml back to regular types
   * @param {string} value the string value
   * @param {*} a native type
   */
  function fixValue (value) {
  
    // is it truthy/falsely
    var lowerValue = value.toLowerCase().trim();
    
    if (lowerValue === false.toString()) return false;
    if (lowerValue === true.toString()) return true;
    
    // is it a number
    if (isFinite(lowerValue) && lowerValue !== '') return new Number (lowerValue);
    
    // just leave it untouched but trimmed
    return value.trim();
  }
  
}



For more like this, see Google Apps Scripts snippets. Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.






Comments