Click here to Skip to main content
15,885,244 members
Articles / Web Development / ASP.NET
Article

XML2JSON Conversion WebService (Demo: IP Info in AJAX-style)

Rate me:
Please Sign up or sign in to vote.
4.80/5 (11 votes)
13 Jun 2006CPOL7 min read 41.3K   630   32   2
The XML2JSON web service allows conversion of any kind of XML in to JSON format.

Introduction

The JSON (JavaScript Object Notation) format seems to be a fat-free alternative for XML in data-interchange. As XML is the best method for document-interchange, JSON could be the best way for data-interchange. Its simplicity, its readability, its integration with some programming and scripting languages (Python and JavaScript – built-in, others – need just a small amount of code) could make it the best candidate.

What is JSON? It is a simple data description format which is based on name-value pairs and collections. It can handle hierarchies and heterogeneous collections, and can make difference between numeric and string values. Let’s examine an example of JSON data format:

JSON
{
"COLLECTION": {
"dateCreated": "01-04-2000", 
"BOOKS": [
{"value": "Splish SplashPaula ThurmanScootney250", 
"TITLE": { "value": "Splish Splash"}
,  "AUTHOR": { "value": "Paula Thurman"}
, "PUBLISHER": { "value": "Scootney"}
,  "PRICE": { "value": 250}
             }
, {"value": "Lover BirdsCynthia RandallLucerne Publishing200", 
"TITLE": { "value": "Lover Birds"}
,  "AUTHOR": { "value": "Cynthia Randall"}
,  "PUBLISHER": { "value": "Lucerne Publishing"}
,  "PRICE": { "value": 200}
}
, {"value": "The Sundered GrailEva CoretsLucerne Publishing100", 
"TITLE": { "value": "The Sundered Grail"}
,  "AUTHOR": { "value": "Eva Corets"}
,  "PUBLISHER": { "value": "Lucerne Publishing"}
,  "PRICE": { "value": 100}
}
]
}
}

You can see that there are various hierarchies in the description above. The structure is heterogeneous and includes primitive values, objects, and collections, and the values are quoted or not, depending on their types.

A big advantage for JSON against XML is the size. The same structure and data in an XML document enlarges the data length, because XML includes node names, while JSON is not redundant with meta-information. Another advantage is, as Sean Kelly said in his article Speeding up AJAX with JSON, parsing JSON is 10 times faster than parsing XML, and the client-side data access will be improved.

You can see more information on the JSON web site.

Transforming the XML

Till now, everything seems to be clear. A data format could help us gather data over the web, with less amount of information and with performance in client-side data access. The question is how to generate data in JSON format, because I suppose you are working with large objects, which contains a lot of data, and re-creating applications to expose data in this format wouldn’t be a nice solution. I have imagined that you have data already in XML format, or you can transform it easily. Unfortunately, it is a step more, but it could be a choice. So, if this condition is accepted, you can go further. The XML2JSON web service generates your XML data in JSON format, provided either by an URL or an XML string. This web service is quite simple, since it uses an XSL stylesheet which does all the work. The web service contains four public web methods:

  • ConvertUrl – converts an XML URL content in JSON format;
  • ConvertXml – converts an XML string in JSON format;
  • GetUrlXmlDocument – returns the XML document corresponding to the URL;
  • GetXmlDocument – returns the XML document corresponding to the XML string.

There are two private methods:

  • SetProxy – serves as helper for web sites that are over a proxy;
  • Transform – performs an XmlDocument transformation in JSON format, using the xml_2_json.xsl stylesheet.

xml_2_json.xsl stylesheet

The essential point of the stylesheet is its recursive nature. The XSL navigates through the XML DOM nodes, and applies a template which decides whether to create an empty object, an object with values, or an array for JSON output. It checks carefully where a comma is needed, or where to close a bracket for a collection ([) or a brace for an object ({).

The main template which matches all the nodes (match="*") has a mode attribute equal to "object". It checks if the object has attributes, inner value, and children. If there is an empty node, it will be created, with an empty value. The node attributes and inner text are created as values for the corresponding object. If the node has children, the template checks if there are multiple nodes with the same name. If so, the template will apply a template in "array" mode, which will build the array elements. Of course, array elements can be complex, so the template in "array_element" mode will apply the template in "object" mode for its children recursively. If the current node has children with different names (there is no array approach), the template in "object" mode will apply itself recursively for the children.

The object name is just the local-name, to avoid naming problems for the JSON object created later.

The template used to choose between the "array" mode and the simple children recursivity is used to create a variable, which is 1 if it is an array, or 0 if not. Checking if the nodes are in "array" (that means there are multiple nodes on the same level with the same name) is performed using the following-sibling:: and preceding-sibling:: axes. If there are nodes above or below the context node which have the same name as the context node ($name=local-name(following-sibling::*) or $name=local-name(preceding-sibling::*), where $name is the context node name), then it must create the array.

The empty nodes are checked using the normalize-space() function. If the content is not empty, then a member called "value" is created. The templates create members values with respect to their data type, by quoting string values and non-quoting numeric values. This feature is ensured by comparing the content converted to a number with NaN (string(number(.)) = 'NaN').

The "," separator is added only for those nodes which are not the last in the collection on the same level (that is, position() < last()).

The array elements and objects are prefixed and postfixed with "{" and "}". The objects and array names are postfixed with ":". The array elements are included between "[" and "]".

The content is trimmed (normalize-space(.)) and escaped. The escaping is done using the msxsl:script feature, with a function called addslashes which escapes the quote character.

The entire stylesheet content is listed below:

xsl
<?xml version="1.0" encoding="UTF-8" ?>
   <xsl:stylesheet version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
      xmlns:js="http://www.json.org">
<msxsl:script language="JScript" implements-prefix="js">
function addslashes(arg)
{
    var s = arg;
    var s1, s2, re;
    s1 = "\""; s2 = "\\\""; 
    re = new RegExp(s1, "ig");
    s = s.replace(re, s2);
    //s1 = "\\x5C"; s2 = "\\\\";
    re = new RegExp(s1, "ig");
    s = s.replace(re, s2);
    //s1 = "\\x2F"; s2 = "\\/";
    re = new RegExp(s1, "ig");
    s = s.replace(re, s2);
    return s;
}
</msxsl:script>
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/">{
    <xsl:apply-templates select="*" mode="object"/>
}
</xsl:template>

<xsl:template match="*" mode="object">
<xsl:if test="count(*) = 0 and count(@*) = 0 and normalize-space(.) = ''">
"<xsl:value-of select="local-name()"/>": ""
<xsl:if test="position() < last()">, </xsl:if>
</xsl:if>
<xsl:if test="count(*) = 0 and (count(@*) > 0 or normalize-space(.) != '')">
"<xsl:value-of select="local-name()"/>": { 
   <xsl:apply-templates select="@*"/>
   <xsl:if test="normalize-space(.) != ''">
   <xsl:if test="count(@*) > 0">, 
   </xsl:if>"value": 
     <xsl:if test="string(number(.)) = 'NaN'">"
   </xsl:if><xsl:value-of select="js:addslashes(normalize-space(.))"/>
   <xsl:if test="string(number(.)) = 'NaN'">"</xsl:if></xsl:if>}
<xsl:if test="position() < last()">, </xsl:if>
</xsl:if>
<xsl:if test="count(*) > 0">
    <xsl:if test="count(@*) > 0">"<xsl:value-of select="local-name()"/>": {
            <xsl:apply-templates select="@*"/>
            <xsl:if test="normalize-space(.) != ''">
            <xsl:if test="count(@*) > 0">, 
            </xsl:if>"value": 
              <xsl:if test="string(number(.)) = 'NaN'">"
            </xsl:if><xsl:value-of 
              select="js:addslashes(normalize-space(.))"/>
            <xsl:if test="string(number(.)) = 'NaN'">"
            </xsl:if></xsl:if>
    </xsl:if>
    <xsl:variable name="is_array">
        <xsl:apply-templates select="*" mode="is_array"/>
    </xsl:variable>    
    <xsl:choose>
        <xsl:when test="contains(string($is_array), '1')">
        <xsl:if test="count(@*) > 0">, </xsl:if>"
        <xsl:value-of select="local-name()"/>": [        
            <xsl:apply-templates select="*" mode="array_element"/>
]
<xsl:choose>
    <xsl:when test="count(@*) > 0 and position() < last()">}, </xsl:when>
    <xsl:when test="count(@*) > 0">}</xsl:when>
    <xsl:when test="position() < last()">, </xsl:when>
</xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:if test="count(@*) > 0">, </xsl:if>
            <xsl:apply-templates select="*" mode="object"/>
<xsl:choose>
    <xsl:when test="count(@*) > 0 and position() < last()">}, </xsl:when>
    <xsl:when test="count(@*) > 0">}</xsl:when>
    <xsl:when test="position() < last()">, </xsl:when>
</xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:if>    
</xsl:template>

<xsl:template match="*" mode="array_element">{
    <xsl:apply-templates select="@*"/>
    <xsl:if test="normalize-space(.) != ''">
    <xsl:if test="count(@*) > 0">, 
    </xsl:if>"value": 
      <xsl:if test="string(number(.)) = 'NaN'">"
    </xsl:if><xsl:value-of select="js:addslashes(normalize-space(.))"/>
    <xsl:if test="string(number(.)) = 'NaN'">"</xsl:if></xsl:if>
    <xsl:if test="count(*) > 0">
    <xsl:if test="(count(@*) > 0 or normalize-space(.) != '')">, 
      </xsl:if><xsl:apply-templates select="*" mode="object"/></xsl:if>
}
<xsl:if test="position() < last()">, </xsl:if>
</xsl:template>

<xsl:template match="@*"><xsl:if test="position() > 1">, 
  </xsl:if>"<xsl:value-of select="local-name()"/>": 
    <xsl:if test="string(number(.)) = 'NaN'">"</xsl:if>
  <xsl:value-of select="js:addslashes(normalize-space(.))"/>
  <xsl:if test="string(number(.)) = 'NaN'">"</xsl:if></xsl:template>

<xsl:template match="*" mode="is_array">
    <xsl:variable name="name" select="local-name()"/>
    <xsl:choose>
        <xsl:when test="$name=local-name(following-sibling::*) 
          or $name=local-name(preceding-sibling::*)">1</xsl:when>
        <xsl:otherwise>0</xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Client-side approach

The client-side approach for JSON format is easy in JavaScript or Python because JSON is a sub-set for both languages. JavaScript parses and evaluates JSON content using the eval function. The example is trivial:

JavaScript
var obj = eval("(" + strJSONContent + ")");

If the strJSONContent variable contains valid JSON data, then an object will be created with properties, arrays, and so on. Otherwise, an error occurs and an exception is thrown. Unfortunately, eval parsing doesn’t retrieve the line or character number where the error occurs, and if the JSON content is significant, it is difficult to debug it.

For the example in the beginning of this article, the object properties can be accessed like this: obj.COLLECTION.BOOKS[1].PUBLISHER.value, and this will return "Lucerne Publishing".

Demo application

GetIPInfo XML2JSON demo application

The GetIPInfo demo application is provided with the web service code. It is an ASP.NET application which "web references" the XML2JSON web service. The application goal is to retrieve information about an IP address. This application acts as a wrapper for the web service, to make it possible to use AJAX, since the AJAX XmlHttp object can’t access a web service directly. The Microsoft WebService behaviors (implemented in the webservice.htc file) solves this problem, but they don’t offer support anymore and the behaviors are only IE compatible.

The application contains cross-browser client code, so it works on MSIE, Firefox, and Opera.

There are two aspx pages:

  • ip_info.aspx - responsible for retrieving IP information (accessing Show My IP web site and returning information in JSON text or XML);
  • util.aspx - utility page for testing the XML2JSON web service.

The page index.html contains functions that access the ASP.NET application using AJAX.

There is a text box for IP address you want to retrieve information about. The three buttons near the text box allow the user to retrieve information using XML and JSON. Direct access to the site is allowed only in IE (in Firefox and Opera, permission is denied).

The cross-browser code for creating an XmlHttp object is:

JavaScript
var xmlHttpReq = null; // Mozilla/Safari 
if (window.XMLHttpRequest) { 
xmlHttpReq = new XMLHttpRequest(); 
} // IE 
else if (window.ActiveXObject) { 
xmlHttpReq = new ActiveXObject("MSXML2.XMLHTTP"); 
}

The code for calling the web application in JSON-style is:

JavaScript
xmlHttpReq.open("GET", "ip_info.aspx?op=GetJSON&ip=" + strIP, true); 
xmlHttpReq.setRequestHeader('Content-Type', 'text/xml');

The last parameter for the open method is the asynchronous mode. If it is true, then a handler for onreadystatechange with a filter for readyState == 4 ("complete") must be added:

JavaScript
xmlHttpReq.onreadystatechange = function() { 
    if (xmlHttpReq.readyState == 4) { 
        var txtResult = document.getElementById("txtResult"); 
        if(txtResult == null) { 
            alert(xmlHttpReq.responseText); 
        } 
        else { 
            txtResult.value = xmlHttpReq.responseText; 
            var spnSize = document.getElementById("spnSize"); 
            if(spnSize != null) { 
                spnSize.innerHTML = xmlHttpReq.responseText.length; 
            } 
            try { 
                var objIPInfo = eval("(" + xmlHttpReq.responseText + ")"); 
            } catch(e) { 
                alert("Number: " + e.number + "\nName: " + e.name + 
                      "\nMessage: " + e.message + 
                      "\nDescription: " + e.description); 
            } 
            showIPInfoJSON(objIPInfo); 
        } 
    }
}

The last statement is the send method call:

JavaScript
xmlHttpReq.send(null);

The showIPInfoJSON function for JSON navigates through the evaluated JSON object. The members in the JSON string are now properties of the objIPInfo object, and are called to retrieve values:

JavaScript
if(objIPInfo.lookup_ip != null)
  div.innerHTML += "Lookup IP: " + objIPInfo.lookup_ip.value + "<br/>";

The same thing does the function showIPInfoXml for XML, but in an XML DOM specific way:

JavaScript
node = xmldoc.getElementsByTagName("lookup_ip")[0]; 
if(node != null && node.firstChild != null) { 
    div.innerHTML += "Lookup IP: " + node.firstChild.data + "<br/>"; 
}

The utility page allows testing more complex XML web sites or XML content. It has some predefined XML web sites such wap.yahoo.com, or WSDL description for web services (e.g.: WorldCup Football 2006), or RSS output (CodeProject RSS feed for new articles).

Conclusion

It is your choice whether to use XML or JSON. It depends on what amount of data you want to gather. The JSON format couldn’t replace XML. It is an easy alternative to collect info. In collaboration with AJAX, these data and document formats help programmers to access information in a modern and elegant manner.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer Telstra Internet
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionXSLT Issue? Pin
etatmirtna14-Dec-06 6:21
etatmirtna14-Dec-06 6:21 
AnswerRe: XSLT Issue? Pin
Dan Radu15-Dec-06 0:14
Dan Radu15-Dec-06 0:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.