Smackdown: XML vs. JSON

Smackdown: XML vs. JSON

A recent post about the Balisage Markup Conference brought out people on both sides who argued that the battle is over, that XML is indeed dead, and that JSON has won. Other than the question I had that I never really thought there was a war again, I thought it would be worth spending some time talking about the fact that XML is in fact quite alive and well thank you, and that JSON, while immensely useful, is not the panacea that its proponents argue.

One of the most frequently cited arguments is that XML is verbose and cryptic, while JSON is neither. I'd say that this has more to do with what examples are given. For very simple objects, you can make this argument:

// JSON declaration of a person:
?
{
     "type":"Person",
     "id":"person:JaneDoe",
     "name":"Jane Doe",
     "birthdate":"06/21/1993",
     "gender":"female"
}?

<!-- XML -->
<Person id="person:JaneDoe">
      <name>Jane Doe</name>
      <birthdate>1993-06-21</birthdate>
      <gender>female</gender>
</Person>
??

Okay, so 131 characters for JSON vs. 135 for XML. As a serialization, you probably do save a few characters here and there with JSON. But, not so fast. Let's try a somewhat different bit of data.

<!-- XML -->
<P class="first">
  ?<Q>Why,</Q> <SPAN role="speaker">Alice</SPAN>
? wondered, <Q>is the <I>bottle</I> <B class="red">red</B>?</Q>
?</P>

This is a far more typical use case for XML, and is 128 characters.

Represent this in JSON:

// JSON
[{"type":"P"?,"_attributes":{"class":"first"},
"seq":[{"type":"Q","text":"Why,"},{"text"," "},
{"type":"SPAN","_attributes":{"role":"speaker"},"text":"Alice"},
{"text":" wondered,"},{"type":"Q","seq":[{"text":"is the ",
{"type":"I","text":"bottle"},{"text":" "},{"type":"B","_attributes":
{"class":"red"},"text":"red"},{"text":"?"}]}}]

This is 335 characters, or about 2 1/2 times the number of characters to represent the same information. Visually, it's much more cryptic, and it has to make several assumptions that aren't standardized (such as the use of _attributes to identify the XML attributes).

But that's HTML, right? Well, yes. There is a flavor of HTML called XHTML. It has all the same elements and some slight (and well-documented) variations on attributes, but it is basically the same structure.

Oh, and then there's namespaces. Namespaces are dreadful things in Javascript, an abomination upon the face of the earth. Okay, so let's be correct here:

<P class="first" xmlns="https://www.w3.org/1999/xhtml">
?<Q>Why,</Q> <SPAN role="speaker">Alice</SPAN> wondered,
<Q>is the <I>bottle</I> <B class="red">red</B>?</Q></P>

We've added 36 characters. Those characters can tell a processor (or XSLT) what exactly this sequence of characters are meant to represent, and once again the namespace is standardized. It's really no different than saying "org.w3.www.py1999.html.*" as a Java import declaration.

Curiously enough, there are no mechanisms to tell a JSON parser what that JSON is meant for, or whether in fact it is schematically valid. What does "valid" mean? It means that there is a schema, or definition, that indicates what elements are allowed where, in what quantity, with that constraints. Ten years after JSON was introduced, there is still no single canonical standard for determining structural validity. With XML, I can either create a catalog that binds namespaces to specific schema documents (written in the XML Schema Definition language, or XSD) or I can directly add a reference to the document explicitly:

<!-- XML -->
<P class="first" xsi:schemaLocation="/path/to/schema.xsd"
    xmlns="https://www.w3.org/1999/xhtml"
    xmlns:xsi=?"https://www.w3.org/2001/XMLSchema-instance"??>
?<Q>Why,</Q> <SPAN role="speaker">Alice</SPAN> wondered,
<Q>is the <I>bottle</I> <B class="red">red</B>?</Q></P>

Now this is not to say that there aren't schema languages for JSON. As with so much of the language, there are actually several different ones, with https://json-schema.org/ being most authoritative. However, this is only just beginning to become standardized, and is not yet widely used. In time, it may be (I rather hope it is, as there are very real use cases for schemas that move beyond validation), but it is almost certain that it will go through a couple of major iterations before it is heavily used.

So, let's talk a little bit about parsing and querying. Now, one of the first things to be defined in XML was a selection language called XPath. It's had nearly twenty years of development, and has become, over that period, remarkably robust. Let's say that I wanted to extract the quote from the above paragraph. Because it spans two distinct elements, you end up having to get both nodes then concatenating them. You can write a Javascript class that takes advantage of the native XML support built into all browsers:

// XPath Class Definition
// Copyright 2017 Kurt Cagle?

class XPath {
  constructor(document=document,nsmap={}) {
    this.doc = document;
    this.nsmap = nsmap;
    this.resolver = this.namespaceGen(nsmap);
  }

namespaceGen(namespaces){
  return function(prefix) {
  var ns = namespaces;
  return ns[prefix] || null;
  }
}  
 
getNodes(context=this.doc,xpathStr){
   if (xpathStr==null){
     xpathStr = context;
     context = this.doc;
   }
   var  iterator = this.doc.evaluate(xpathStr, context, 
          this.resolver,XPathResult.ANY_TYPE,null);
   var nodeArray = [];
   var node = iterator.iterateNext();
   while (node){
      nodeArray.push(node)
      node= iterator.iterateNext();
      }
  return nodeArray;
   }
  
get(context=this.doc,xpathStr){
  var arr = this.getNodes(context,xpathStr);
  return arr.map((obj)=>this.nodeStr(obj));
}
  
static dataIsland(id){
  var xmlSource = document.getElementById(id).textContent;
  return (new DOMParser())
     .parseFromString(xmlSource,"application/xml")
  }
  
nodeStr(node){
    return node.textContent
  }
}?

From this, you can retrieve the nodes as strings and join them:

const doc = (new XMLDocument()).parseFromString(xml);
const xpath = new XPath(doc);
const quote = xpath.get(`/P[@class='first']/Q/string()`)).join('');
//=> "Why is the bottle red?"

Back in JSON-land, things are a bit more complicated.

const doc = JSON.parse(json);
function stringify(seq){return 
      seq.map((item)=>item.seq?stringify(item.seq):item.text};
const quote = doc.filter((element)=>element.type =="P" && 
    element._attributes["class"]=="first")
    .filter((pElement)=>pElement.seq)
    .filter((element)=>element.type=="Q")
    .map((qElement)=>qElement.text||stringify(qElement.seq))
   .join("");
    

I have to write a recursive function to retrieve all the text sub-segments of each Q element, and have to know intimately what the structure looks like. I also can't do _attributes.class, because class is a reserved word in Javascript. Finally, this is only made possible by using higher order functions - this becomes even more complex in earlier versions of Javascript. So the "nominal" savings involved with JSON translates into more complex selection code.

At this point, the die-hard Javascript developer is going "well, okay, maybe it's not as good for HTML-like code, but I'm more interested in data. There it really shines.

Not so fast. Let's say you have a typical data structure - an address block:

<!-- XML -->
<Persons>?
   <Person id="person:JaneDoe">
      <name>Jane Doe</name>
      <birthdate>1993-06-21</birthdate>
      <gender>female</gender>
      <Addresses>
          <Address id="address:AddrJD1">
              <street>123 Sesame Street</street>
              <city>New York</city>
              <state>NY</state>
              <postalCode>10001</postalCode>???
          </Address>???
          <Address id="address:AddrJD2">
              <street>1313 Mockingbird Lane</street>
              <city>Arkham</city>
              <state>MA</state>
              <postalCode>02118</postalCode>???
          </Address>???
       </Addresses>
   </Person>
   <Person id="person:johnDoe">
      <name>John Doe</name>
      <birthdate>1989-03-11</birthdate>
      <gender>male</gender>
        <Addresses>
          <Address id="address:AddrJD3">
              <street>1 Avengers Tower</street>
              <city>New York</city>
              <state>NY</state>
              <postalCode>10006</postalCode>???
          </Address>?
        </Addresses>??
   </Person>??
   ...
</Persons>

//JSON?
{"Persons":{
     "person:JaneDoe":{
          "type":"Person",
          "name":"Jane Doe",
          "birthdate":"1993-06-12",
          "gender":"female",
          "Addresses":{
                "address:AddrJD1":{
                     "type":"Address",
                     "street":"123 Sesame Street",
                     "city":"New York",
                     "state":"NY",
                     "postalCode":"10001"
                      },
                "address:AddrJD2":{
                     "type":"Address",
                     "street":"1313 Mockingbird Ln",
                     "city":"Arkham",
                     "state":"MA",
                     "postalCode":"02118"
                      }
                }
          },
     "person:JohnDoe":{
          "type":"Person",
          "name":"Jane Doe",
          "birthdate":"1993-06-12",
          "gender":"male",
          "Addresses":{
                "address:AddrJD3":{
                     "type":"Address",
                     "street":"1 Avenger Tower",
                     "city":"New York",
                     "state":"NY",
                     "postalCode":"10001"
                      }
                }
          }
    }
}

Again, the legibility issue is largely a wash. This provides a way of creating an object exclusive database, where the id is used as a key. This is the arrangement often used by JSON databases.

Now, ask the question: find all people who have addresses in Massachusetts.

Again, on the XML side, this is a simple proposition:

const persons = Array.from(doc 
        .xpath(`//Person[.//Address[state='NY']]`))

Notice something here. The // operator lets you find all items in the tree that matches the particular element, not just the items that are immediate children. The [] operator, discussed before, is what's called a predicate, and translates roughly into "in which". Finally the "." operator identifies the current context - or the place where you are in the tree. Thus,

//Person[.//Address[state='MA']]

can be read as "Find all Person elements in which, for each of these elements, there is an Address element in the tree in which the state is 'MA'".

The power of this is that I don't need to know anything about any element in the tree beyond these three. In most implementations of XPath, by the way, once the tree is parsed, this is also pretty efficient, because internally the element names are usually cached, so finding subtree content can often be a quick lookup away.

So, Javascript query ... hmmm. We're going to have to write a tree-filter for this one. A tree filter (and it's associated tree walker) is a recursive function that walks down the nodes of a JSON tree, returning a node whenever it encounters one that satisfies the particular condition. It is similar to the Array filter method, but works on trees, taking a function as the filter argument. This also shows how the arrow notation of ES6 really comes in handy.

Object.prototype.treeFilter = function(filter){
   const obj = this;
   const fold = (a,b)=>a.concat(b);
   Object.prototype.treeWalker=function(filter){
    const elt = this;
    if (filter(elt)){
         return elt
         }
     else {
       return Array.from(Object.keys(elt))
         .map((key)=>(typeof elt[key] === "object")?(elt[key])
            .treeWalker(filter):null)
         .filter((item)=>item != null);
       }
     };

return (obj.treeWalker(filter))
          .reduce(fold,[]).reduce(fold,[])
          .filter((item)=>!Array.isArray(item))}


var maPeople = doc.treeFilter((elt)=>elt.type=='Person')
    .treeFilter((elt)=>(elt.state=="MA"))
// Returns an array of one person (Jane Doe), 
// who lived in Massachusetts.

So, not all that painful, but conceptually it takes a lot to figure out, and this is without talking about a data structure that includes arrays (I will, perhaps meanly, leave that as an exercise to the reader).

Note that this gets even harder when dealing with other languages that have more rigid type requirements. Most languages do have XML to object model filters, and many have some form of XPath implementation even if occasionally complex.

The idea here is not to show conclusively that XML is superior to JSON for all data, but to show that XML as a tool-set is considerably more robust than is commonly assumed, especially for large, complex data objects, and narrative structures. XPath alone is usually worth mastering precisely because of these considerations. This doesn't even begin to include the transformative power of XSLT, for which no real analog exists for JSON.

One final point is worth considering as well. There are a growing number of libraries that provide for XML to JSON (and vice versa) conversion. I've had good luck especially with the xml2json.js library (https://www.npmjs.com/package/xml2json), which provides a very straightforward way of transforming between the two structures. Additionally, inline XML is now much easier to create using template literals. For instance, if you wanted to create an (X)HTML table you can do it trivially within Javascript, using the ` back-tick character. Using the doc object from above the following let's you create an associated table:

var tableTemplate = (data)=> `
<table>
   <thead>?
      <tr>
        <th>ID</th>
        <th>Name</th>
        <th>DateOfBirth</th>
        <th>Gender</th>
        <th>Most Recent States</th>
      </tr>
    </thead>
    <tbody>${Array.from(Object.keys(data.Persons))
            .map((key)=>data.Persons[key])
            .map((person)=>rowTemplate(person,key)).join('')
}    </tbody>
</table>?`;
var rowTemplate = (person,key)=>
??`      <tr>
         <td>${key}</td>
         <td>${person.name}</td>
?         <td>${person.birthdate}</td>
??         <td>${person.gender}</td>
??         <td>${Object.keys(person.Addresses)
                 .map((address)=>address.state).join(', ')}</td>
       </tr>`;



var table = tableTemplate(doc);
document
    .querySelector('#personDisplay')
    .innerHTML = table;??
var kittyPryde = {
   id:"person:kittyPryde",
   name:"Kitty Pryde",
   birthdate:"1998-03-15", 
   gender:"female",
   Addresses:{"addr:XManSchool":{state:"MA"}}
};
document
    .querySelector("#personDisplay tbody")
    .insertAdjacentHTML("beforeEnd",personTemplate(kittyPryde));

The use, then, of of both the innerHTML setter and the insertAdjacentHTML() command can let you create and update content quickly and efficiently.

You can also store such templates as scripts then reference them, and use such scripts along with tags to cut down on remembering syntax. For instance, the tagged literal functions can simplifying instantiating XML documents in the first place. Tagged literals are specialized functions that tag literals as arguments, decomposing them as alternating strings and items (found by evaluating the ${expr} construct in the template literal, as shown above. The xml tag literal, can turn an XML string into an XML DOM Document:

function xml(strings,...items){
     var resultStr = items
         .map((value,index)=>`${strings[index]}${items[index]}`)
         .concat(strings[strings.length-1]).join("");

     return (new DOMParser()).parseFromString(resultStr,"text/xml");
}

var c = 10;

var doc = xml `<foo><bar>${c}</bar></foo>`;
console.log(doc)
-> #document
   <foo>
      <bar>10</bar>
   </foo>?


Th e upshot of all of this is simple. XML is a tool in the web developer's tool-chest, nothing more, nothing less. It can be used to simplify queries of local (and remote) datasets, can insure compliance and validation, and can be easily created with template literals and tagging. It's not better than JSON at all things, but when applications become complex, it's often the better choice.

Kurt Cagle uses both JSON and XML, each where they are appropriate.

David R.R. Webber

Consultant specializing in Election Integrity and Cloud AI frameworks and Cryptology technologies.

7 年

Great insights as ever. IMHO - the reason folks think JSON is great is for quick and dirty localized use within a web or mobile application. And their IDE tooling hides all the plumbing for them. This all punts on downstream concerns. As soon as other external entities enter the picture you need more rigour and then XML. Plus totally agree XPath is gold and plus XSLT for content handling. If you want to see how powerful XML can be look at the feature set in the CAM Editor toolkit - handling XML, JSON, SQL, UML, Mindmaps, Excel, XSD, JS Forms, Pencil mockups all from one XML template layout.

回复
Alexander Novitsky

Lead Developer at Luxoft

7 年

This article is a good example how an ass-handed person could take a good thing and make it bad.

回复
Michael Andrews

Content Architect | Strategist | Evangelist

7 年

I'm surprised you don't mention JSON-LD, which allows schemas using JSON syntax. The value of JSON is not so much readability or concision, as it is that is works well with Javascript. Raw Javascript is certainly more verbose than XPath, but most people use libraries that allow simple fetching of values without the complicated code. These libraries let one focus on the attribute desired, rather than worrying about where in a tree the attribute resides. JSON (and JSON-LD) presume a data orientation. They are geared toward reusing data (especially for APIs). Embedding metadata within content to flag data elements one wants to reuse seems highly inefficient. XML seems strong for repurposing text in different variants, but JSON seems stronger for enabling data reuse. Seems one could combine them by separating data as JSON, and inserting those JSON values within an XML template that could generate different versions of the complete content.

回复
David Porter

Retired Service Manager, Public Cloud Infrastructure at Boeing Information Technology

7 年

XML, and XSLT (and XPATH) are hugely productive for my own work. I have not gotten a grip on JSON yet, the hieroglyphics non-human-readable nature of in particular array structures just seem unreadable to me.

Eliud Kagema

Software Developer | Data Engineer

7 年

JSON all the way.

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了