10.7. XML alternative notations

In this document, we have been promoting XML as the ideal exchange language between systems because of its standardization and its wide range of applications. Unfortunately XML needs a complex infrastructure (parser, validator, transformer, etc.) in order to profit from its power. In some cases, this may prove to be an overkill. This is why we will now describe two alternative human readable and writable notations for information interchange between systems. This will show that XML is not the only exchange formalism and will highlight the advantages and disadvantages of each approach.

10.7.1. JSON

The AJAX technology, built upon the XMLHTTPRequest (XHR) system call that exchanges information between a browser and a server in order to update a part of a Web page without reloading it entirely. This allows in certain cases a better user experience (dynamic suggestions, on-the-fly server-side validation, etc.) especially when a Web page is used as interface for an interactive system.

As its name implies, XHR involves an XML information exchange between the server and the browser. XML must then parsed by the Javascript engine of the browser for each exchange in order to create the corresponding Javascript data structure in memory. If E4X is not implemented in the Javascript interpreter, using a full XML parser may be prohibitive in both time and memory usage for simple data exchange. A simpler alternative has been proposed: JavaScript Object Notation (JSON) which is based on the conventional literal object notation of Javascript. In this case, creating a Javascript data structure is just a matter of evaluating the received data string from the server. Because the eval function is already included in all Javascript interpreters, there is thus no need for an external parser.

JSON encodes the information by means of the primitive data structures of Javascript built on the following types of values:

primitive objects
numbers, strings (between double quotes), booleans (true or false) and null;
complex objects
arrays (elements between square brackets) or objects (key-value pairs between braces). Arrays or objects elements are separated by a comma. A key is a string and it is separated from the corresponding value by a colon.

Complex objects components are accessed using the dot notation as for any other Javascript object. If the name of an element is a simple identifier then it can be used directly (e.g. wine.origin.country) but if it contains a dash which is a not a legal Javascript identifier, the we must use the indexing mechanism such as wine["is-red"]. It is similar to what we have shown for E4X in the previous section, but it does not allow for all XPath-like possibilities provided by the direct XML literal notation.

In order to show the similarities between XML and JSON, we show a small XML wine list in Example 10.35 and its JSON equivalent in Example 10.36. A tree with more than one child is translated into an object with a single key corresponding to the root of the tree and its value is another object containing the attributes and children. The attributes are simple key-value pairs and the children are nodes that are themselves objects. Similarly named nodes are combined within an array to allow indexing. If the content of Example 10.36 is kept in the Javascript variable s, then parsing is a simple matter of doing root = eval(s);[5]. In this case, the name of the second wine of this list can be accessed in Javascript with root["wine-list"].wine[1].name.

Example 10.35. [WineList.xml] A small wine list in XML

  1 <?xml version="1.0" encoding="UTF-8"?>
    <wine-list>                                                          (1)
        <wine name="Domaine de l'Île Margaux"                            (2)
            appellation="Bordeaux supérieur">                            (3)
  5         <is-red>true</is-red>                                        (4)
            <origin>
                <country>France</country>
                <region>Bordeaux</region>
            </origin>
 10         <price>22.80</price>                                         (5)
            <year>2002</year>
        </wine>
        <wine name="Riesling Hugel" appellation="Alsace">                (6)
            <is-red>false</is-red>
 15         <origin>
                <country>France</country>
                <region>Alsace and East</region>
            </origin>
            <price>17.95</price>
 20         <year>2002</year>
        </wine>   
    </wine-list>

1

Root element of a small data-oriented XML document.

2

First wine element, with two attributes.

3

An attribute with a string value.

4

An element with a boolean value.

5

An element with a numerical value.

6

Second wine element with two attributes.


Example 10.36. [WineList.json] A small wine list in JSON

The same content as in Example 10.35, transformed in JSON, the same callout numbers in both listings correspond to the same elements.

  1 {
    "wine-list": {                                                       (1)
       "wine":[{                                                         (2)
             "name": "Domaine de l'Île Margaux",                         (3)
  5          "appellation": "Bordeaux supérieur",
             "is-red": true,                                             (4)
             "origin": {
                "country": "France",
                "region": "Bordeaux"
 10          },
             "price": 22.80,                                             (5)
             "year": 2002
          },{
             "name": "Riesling Hugel",                                   (6)
 15          "appellation": "Alsace",
             "is-red": false,
             "origin": {
                "country": "France",
                "region": "Alsace and East"
 20          },
             "price": 17.95,
             "year": 2002
          }
       ]
 25 }}

1

Root element translated as a single key-value pair.

2

All wines will be stored under a single key corresponding to an array of values.

3

An attribute is transformed into a key-value pair with a string value within quotes.

4

A boolean value is written without quotes.

5

A numerical value is written without quotes.

6

Second wine of the array.


The JSON version given above was produced by Example 10.37, an XSLT stylesheet for translating an XML file in JSON format. We will describe later some restrictions that should be put on an XML file so that its translation to JSON is meaningful and produces legal Javascript.

Example 10.37. [xml2json.xsl] XSLT stylesheet to convert an XML file into JSON.

  1 <!DOCTYPE stylesheet [                                               (1)
    <!ENTITY cr "<xsl:text>
    </xsl:text>">
    ]>
  5 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:gl="http://www.iro.umontreal.ca/lapalme"
        version="2.0">
        <xsl:strip-space elements="*"/>
 10     <xsl:output omit-xml-declaration="yes"/>
        
        <xsl:template match="/">                                         (2)
            <xsl:text>{</xsl:text><xsl:apply-templates>
                <xsl:with-param name="indent"  select="0"/>
 15         </xsl:apply-templates><xsl:text>}</xsl:text>
        </xsl:template>
        
        <!-- end current line, indent $str by $nb spaces -->
        <xsl:template name="doIndent">                                   (3)
 20         <xsl:param name="nb" as="xsd:integer"/>
            <xsl:param name="str" as="xsd:string"/>
            &cr;<xsl:value-of 
                select="concat(string-join(for $i in 1 to $nb return ' ',''),$str)"/>
        </xsl:template>
 25     
        <!-- produce a JavaScript String:
             if the parameter is a number or a boolean, output the string;
             otherwise put the string between quotes escaping quotes within it-->
        <xsl:function name="gl:jsString">                                (4)
 30         <xsl:param name="s"/>
           <xsl:value-of select="
             if (string(number($s))!='NaN' or matches($s,'true|false','i')) then $s 
             else concat('&quot;',replace($s,'&quot;','\\&quot;'),'&quot;')
             "/> 
 35     </xsl:function>
        
        <xsl:template name="outArray">                                   (5)
            <xsl:param name="indent" as="xsd:integer"/>
            <xsl:call-template name="doIndent">
 40             <xsl:with-param name="nb" select="$indent+3" as="xsd:integer"/>
                <xsl:with-param name="str" 
                    select="concat(gl:jsString(string(current-grouping-key())),
                    ':[')"/>
            </xsl:call-template>
 45         <!-- output its contents "recursively" -->
            <xsl:apply-templates select="current-group()">
                <xsl:with-param name="indent" select="$indent+6" as="xsd:integer"/>
                <xsl:with-param name="outputName" select="false()"/>
            </xsl:apply-templates>
 50         <xsl:call-template name="doIndent">
                <xsl:with-param name="nb" select="$indent+3" as="xsd:integer"/>
                <xsl:with-param name="str" select="']'"/>
            </xsl:call-template>
        </xsl:template>
 55     
        <!-- an attribute is its name followed by its text value -->
        <xsl:template match="@*"><xsl:param name="indent" as="xsd:integer"/>  (6)
            <xsl:call-template name="doIndent">
                <xsl:with-param name="nb" select="$indent" as="xsd:integer"/>
 60             <xsl:with-param name="str" 
                    select="concat(gl:jsString(name()),': ',gl:jsString(.))"/>
            </xsl:call-template>
        </xsl:template>
        
 65     <!-- a text node is the value of its content -->                 (7)
        <xsl:template match="text()">
            <xsl:value-of select="gl:jsString(normalize-space(.))"/>
        </xsl:template>
        
 70     <!-- any child node -->
        <xsl:template match="*">                                         (8)
            <xsl:param name="indent" as="xsd:integer"/>
            <xsl:param name="outputName" select="true()"/>
            <!-- output the name of the tag if so desired -->
 75         <xsl:if test="$outputName">                                  (9)
                <xsl:call-template name="doIndent">
                    <xsl:with-param name="nb" select="$indent"/>
                    <xsl:with-param name="str" select="gl:jsString(name())"/>
                </xsl:call-template>
 80             <xsl:text>: </xsl:text>
            </xsl:if>
            <xsl:choose>
            <!-- node with a single child text node which must be non-empty-->
            <xsl:when test="count(text()[string-length(normalize-space(.))>0])=1">
 85             <xsl:apply-templates>                                    (10)
                    <xsl:with-param name="indent" select="$indent+3"/>
                </xsl:apply-templates>
            </xsl:when>
            <!-- any type of nodes -->
 90         <xsl:otherwise>
                <xsl:text>{</xsl:text>
                <!-- loops over all groups of identical tags -->         (11)
                <xsl:for-each-group select="@*|*|text()" 
                                    group-adjacent="local-name()">
 95                 <xsl:choose>
                        <xsl:when test="count(current-group())=1">
                        <!-- a single element group-->
                        <xsl:apply-templates select=".">
                            <xsl:with-param name="indent" select="$indent+3"/>
100                     </xsl:apply-templates>
                        </xsl:when>
                        <xsl:otherwise>
                            <!-- many element groups within braces-->
                            <xsl:call-template name="outArray">
105                             <xsl:with-param name="indent" select="$indent"/>
                            </xsl:call-template>
                        </xsl:otherwise>
                    </xsl:choose>
                    <xsl:if test="position()!=last()">
110                     <xsl:text>,</xsl:text>
                    </xsl:if>
                </xsl:for-each-group>
                <!-- end current group -->
                <xsl:call-template name="doIndent">
115                 <xsl:with-param name="nb" select="$indent"/>
                    <xsl:with-param name="str" select="'}'"/>
                </xsl:call-template>
            </xsl:otherwise>
            </xsl:choose>
120         <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:template>
        
125 </xsl:stylesheet>
    
    

1

Defines an entity for ending a line with a carriage return.

2

Starts the transformation of all node with an indentation of 0.

3

Named template for ending the current line and outputting a string indented with a given number of spaces.

4

Outputs a number, a boolean or a string in the correct Javascript format. It ensures that quotes are escaped within a string.

5

Outputs an array of similarly named elements. The name comes from the current grouping key found on line 92‑11. An opening square bracket is output. The internal elements are converted recursively but by indicating that their name is not to be output. The closing square bracket is finally output.

6

An attribute is converted like a simple element: its name, a colon and the string value.

7

A text node is output but it is first normalized.

8

An element is output as a key-value pair.

9

If the name is to be output (it is the case unless we are dealing with an element within an array), the name is output properly indented, followed by a colon.

10

If it is a non-empty text node, output it properly indented.

11

When it is a complex node, group adjacent similarly named element within an array. When a group has only one element, then output it recursively properly indented. If there are many elements in the group, then use outArray line 37‑5 for its elements. Separate each group with a comma and end with a closing brace.


Example 10.37 shows that it is relatively straightforward to produce JSON output from an XML file. The resulting Javascript string is trivially transformed into a Javascript data structure using the eval function. The browser then does not need a full blown XML parser to access the information from the XML structure.

But one should be aware that there are intrinsic limitations to the JSON notation compared to XML. It is not possible to do validation on the JSON structure as it is possible on XML using a Schema. Some JSON proponents argue that JSON is typed because the notation differentiates between numbers, strings, arrays and sets of key-value pairs while XML is untyped because it is only string. This is the case only when this is considered from the point of view of Javascript without any notion of XML embedded processing. In our view, XML is typed because it can be validated with a Schema but JSON is not...

Transforming XML elements into sets of key-value pairs also loses some information that might be important:

  • the original document order is lost except within arrays that arise from a series of adjacent similarly named elements. In Javascript, key-value pairs within an object are not ordered, they behave like a hash table.

  • there is no distinction between an attribute of an element and a children element as both are translated similarly.

  • there is no defined translation for XML mixed content, i.e. a text node interspersed with other elements. In particular, Example 10.37 will produce illegal Javascript when it encounters mixed content because strings will be output within a key-value pair.

It is also possible to write an XSL stylesheet to transform JSON text into XML. The companion Web site of this document, gives one example in the file

json2xml.xsl

The processing is similar to the one used in Example 9.2 but doing this with a stylesheet implies a recursive processing of the textual input.

Because of the limitations given above, an XML transformed by Example 10.37 and then back with json2xml.xsl will not necessarily be identical: elements might be reordered within a node and an attribute of an element will be transformed into a child node of the element.

10.7.2. YAML

Another data structure notation that has been proposed is YAML[90], which could have meant Yet Another Markup Language, but is instead the recursive acronym of YAML Ain't a Markup Language. As one of the primary intended use of YAML is for configuration files, it was designed to be easy to read and write by humans using standard text editors. This contrasts with XML for which brevity was not a design goal because it was intended to be produced and parsed by machines.

Similarly to JSON, YAML describes tree structured data. The label of the root of the tree is written as a string line followed by colon. If the content is a simple value (string, number or boolean) then it is written on the same line. If the content is a complex value (hash table or array), it is written on the lines that follow, more deeply indented than the start of label of the root.

Example 10.38 shows how the content of our small wine list (Example 10.35) could be written in YAML. The same restrictions (no distinctions between attributes and nodes, no order of nodes, except within arrays) that we have described for JSON also apply to YAML. Parsers for YAML have been written for many programming languages so it means that it is possible to use it for exchanging data between systems written in different languages.

Example 10.38. [WineList.yaml] YAML version of Example 10.35

In order to show the parallel between XML, JSON and YAML, we use the same callout numbers as for Example 10.36.

  1 wine-list:                                                           (1)
      wine:                                                              (2)
        - 
          name: "Domaine de l'Île Margaux"                               (3)
  5       appellation: Bordeaux supérieur
          is-red: true                                                   (4)
          origin: 
            country: France
            region: Bordeaux
 10       price: 22.80                                                   (5)
          year: 2002
        -                                                                (6)
          name: Riesling Hugel
          appellation: Alsace
 15       is-red: false
          origin: 
            region: Alsace and East
            country: France
          price: 17.95
 20       year: 2002
    

1

Root element on a single line with no indentation.

2

Dependent element with an indentation that controls the next lines. The - on the following line indicates the start of an array element. The content of each array element is indented more deeply than the dash. A single value content is put on the same line.

3

A key with a string value. A string is not put within quotes unless it includes some special characters like quotes or newlines.

4

A key with a boolean value.

5

A key with a numerical value.

6

Start of the second wine of the array.


This section has shown simpler alternatives for markup languages for describing tree structured information. In special cases (configuration files, simple information exchanges), they provide enough structuring to be useful but in the more general set-up in which document ordering is important, they do not allow the full expression power of XML.



[5] As some Javascript parsers will consider the first identifier followed by a colon to be the label of a statement, it is usually safer to use root = eval("("+s+")"); to force the parser to consider the string to be an expression.