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.
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:
true
or false
) and
null
;
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><wine name="Domaine de l'Île Margaux"
appellation="Bordeaux supérieur">
5 <is-red>true</is-red>
<origin> <country>France</country> <region>Bordeaux</region> </origin> 10 <price>22.80</price>
<year>2002</year> </wine> <wine name="Riesling Hugel" appellation="Alsace">
<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>
|
Root element of a small data-oriented XML document. |
|
First wine element, with two attributes. |
|
An attribute with a string value. |
|
An element with a boolean value. |
|
An element with a numerical value. |
|
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": {"wine":[{
"name": "Domaine de l'Île Margaux",
5 "appellation": "Bordeaux supérieur", "is-red": true,
"origin": { "country": "France", "region": "Bordeaux" 10 }, "price": 22.80,
"year": 2002 },{ "name": "Riesling Hugel",
15 "appellation": "Alsace", "is-red": false, "origin": { "country": "France", "region": "Alsace and East" 20 }, "price": 17.95, "year": 2002 } ] 25 }}
|
Root element translated as a single key-value pair. |
|
All wines will be stored under a single key corresponding to an array of values. |
|
An attribute is transformed into a key-value pair with a string value within quotes. |
|
A boolean value is written without quotes. |
|
A numerical value is written without quotes. |
|
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 [<!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="/">
<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">
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">
30 <xsl:param name="s"/> <xsl:value-of select=" if (string(number($s))!='NaN' or matches($s,'true|false','i')) then $s else concat('"',replace($s,'"','\\"'),'"') "/> 35 </xsl:function> <xsl:template name="outArray">
<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"/>
<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 -->
<xsl:template match="text()"> <xsl:value-of select="gl:jsString(normalize-space(.))"/> </xsl:template> 70 <!-- any child node --> <xsl:template match="*">
<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">
<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>
<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 -->
<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>
|
Defines an entity for ending a line with a carriage return. |
|
Starts the transformation of all node with an indentation of 0. |
|
Named template for ending the current line and outputting a string indented with a given number of spaces. |
|
Outputs a number, a boolean or a string in the correct Javascript format. It ensures that quotes are escaped within a string. |
|
Outputs an array of similarly named elements. The name comes from
the current grouping key found on line 92‑ |
|
An attribute is converted like a simple element: its name, a colon and the string value. |
|
A text node is output but it is first normalized. |
|
An element is output as a key-value pair. |
|
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. |
|
If it is a non-empty text node, output it properly indented. |
|
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 |
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
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.
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:wine:
- name: "Domaine de l'Île Margaux"
5 appellation: Bordeaux supérieur is-red: true
origin: country: France region: Bordeaux 10 price: 22.80
year: 2002 -
name: Riesling Hugel appellation: Alsace 15 is-red: false origin: region: Alsace and East country: France price: 17.95 20 year: 2002
|
Root element on a single line with no indentation. |
|
Dependent element with an indentation that controls the next
lines. The |
|
A key with a string value. A string is not put within quotes unless it includes some special characters like quotes or newlines. |
|
A key with a boolean value. |
|
A key with a numerical value. |
|
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.