Previous Page
Next Page

Hack 7. Receive Data in JSON Format

Ajax can receive data in efficient and powerful JavaScript Object Notation.

How would you like to use Ajax and receive data from the server as plain old JavaScript objects? Well, you can, using a format called JavaScript Object Notation (JSON). This hack takes information entered by a web user and initiates a server round trip, which returns the data in JSON syntax for the web page's use.

JSON is simple and straightforward, which is probably why a lot of developers like it. JSON-formatted data is appropriate for simple objects that are bundles of properties and values. An example is a server program that pulls product information from a database or cache and returns it to a retail web page in JSON format. Data in JSON format is represented by:

  • An opening curly brace ( {)

  • One or more property names, separated from their values by colons, with property/value pairs separated by commas

  • A closing curly brace (})

The values of each property in the object can be:

  • Simple strings, such as "hello"

  • Arrays, such as [1,2,3,4]

  • Numbers

  • The values true, false, or null

  • Other objects, as in a composition, or an object containing one or more objects

See http://www.json.org for further details.


This is exactly the format of an Object literal in JavaScript. As an example, here is what the information requested of the user in "Use the Request Object to POST Data to the Server" [Hack #2] looks like in JSON format:

{
firstname:"Bruce",
lastname:"Perry",
gender:"M",
country:"USA"
}

Magic JSON

In this section, we'll use a similar HTML page to the one used in "Use the Request Object to POST Data to the Server" [Hack #2], and we'll ask the user for the same information; however, this hack uses JavaScript code and Ajax to handle a JSON return value from the server. Two div elements at the bottom of the HTML page show the JSON return value from the server and then display the object's properties and values in a more friendly fashion.

Here's the code for the HTML page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
<html>
<head>
    <script type="text/javascript" src="js/hack5.js"></script>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Receive JSON response</title>
</head>
<body>
<h3>A Few Facts About Yourself...</h3>
<form action="javascript:void%200" onsubmit="sendData(  );return false">
    <p>First name: <input type="text" name="firstname" size="20"> </p>
    <p>Last name: <input type="text" name="lastname" size="20"> </p>
    <p>Gender: <input type="text" name="gender" size="2"> </p>
    <p>Country of origin: <input type="text" name="country" size="20"> </p>
    <p><button type="submit">Send Data</button></p>
    <div id="json"></div>
    <div id="props"></div>
</form>
</body>
</html>

Figure 1-9 shows what the web page looks like.

Figure 1-9. JSON is calling


The JavaScript code is imported by the script tag and specified by the file hack5.js. The JavaScript sends the user's entered values to the server; because this was discussed in "Use the Request Object to POST Data to the Server" [Hack #2] and other hacks, the code is reproduced here but doesn't go into great detail.

Beware of cross-site scripting (XSS) attacks when evaluating any return values as JavaScript code in this manner. This is a potential threat for any use of eval( ) or the Function-related code discussed in this hack.

As a countermeasure, the client-side JavaScript can filter and inspect the return value (e.g., by looking at the XMLHttpRequest responseText property) for the presence of the expected object property names before the code uses responseText in the eval( ) function (see http://www.perl.com/pub/a/2002/02/20/css.html).


Here's the code for this hack. Below, we'll go over the key parts that handle the return value as a JavaScript object.

var request;
var queryString;   //will hold the POSTed data

function sendData(  ){
    setQueryString(  );
    url="http://localhost:8080/parkerriver/s/json";
    httpRequest("POST",url,true);
}

//event handler for XMLHttpRequest
function handleJson(  ){
    if(request.readyState == 4){
        if(request.status == 200){
            var resp =  request.responseText;
            var func = new Function("return "+resp);
            var objt = func(  );
            var div = document.getElementById("json");
            stylizeDiv(resp,div);
            div = document.getElementById("props");
            div.innerHTML="<h4>In object form...</h4>"+
                          "<h5>Properties</h5>firstname= "+
                          objt.firstname +"<br />lastname="+
                          objt.lastname+ "<br />gender="+
                          objt.gender+ "<br />country="+
                          objt.country;
        } else {
            alert("A problem occurred with communicating between "+
                 "the XMLHttpRequest object and the server program.");
        }
    }//end outer if
}

/* Initialize a request object that is already constructed */
function initReq(reqType,url,bool){
    /* Specify the function that will handle the HTTP response */
    request.onreadystatechange=handleJson;
    request.open(reqType,url,bool);
    request.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded; charset=UTF-8");
    request.send(queryString);
}

/* Wrapper function for constructing a request object.
 Parameters:
  reqType: The HTTP request type, such as GET or POST.
  url: The URL of the server program.
  asynch: Whether to send the request asynchronously or not. */
  
function httpRequest(reqType,url,asynch){
    //Snipped... See Hack #1 or #2
}

function setQueryString(  ){
    queryString="";
    var frm = document.forms[0];
    var numberElements =  frm.elements.length;
    for(var i = 0; i < numberElements; i++){
        if(i < numberElements-1){
            queryString += frm.elements[i].name+"="+
                           encodeURIComponent(frm.elements[i].value)+"&";
        } else {
            queryString += frm.elements[i].name+"="+
                           encodeURIComponent(frm.elements[i].value);
        }
    }
}

function stylizeDiv(bdyTxt,div){
    //reset DIV content
    div.innerHTML=" ";
    div.style.fontSize="1.2em";
    div.style.backgroundColor="yellow";
    div.appendChild(document.createTextNode(bdyTxt));
}

As in this chapter's previous hacks, the initReq( ) function initializes the request object and sends an HTTP request to the server.

The event-handling function for when the response is ready is called handleJson( ). The response is a JSON-formatted text string, as opposed to XML or some other text type. As is, JavaScript interprets this returned text as a string object. Therefore, the code initiates an opening step before the server's return value is interpreted as a JavaScript object literal. (By the way, in this hack, the server takes the request parameters and reformats the parameter names and property values into JSON syntax, prior to sending the reformatted data as its response.)

Special error-handling code is not included here, because these elements require further explanation and are covered by "Handle Request Object Errors" [Hack #8].


Within the handleJson( ) code (highlighted in the previous code sample), the variable resp refers to the HTTP response text, which JavaScript interprets as a string. The interesting stuff occurs in the Function constructor:

var func = new Function("return "+resp);

This code creates a new Function object on the fly and stores the Function in a variable named func. JavaScript coders might note that most functions are predefined and declared in code, or created as function literals. However, in this case we need to define a function body dynamically using a string, and the Function constructor provides the perfect tool.

Thanks to this site for guidance on this code usage: http://www.jibbering.com/2002/4/httprequest.html.

Another method for converting JSON strings that's making its way around the Web goes like this:

var resp =  request.responseText;
var obj = eval( "(" + resp + ")" );

You do not have to use the parentheses characters when using eval( ) and an array, as in:

var resp =  request.responseText;
//resp contains something like "[1,2,3,4]"
var arrObject = eval(resp);


The next line creates a function that returns an object literal, representing the server return value. You then call the function and use the returned object to dynamically display server values on the web page with DOM programming (all without complex object serialization or a page refresh!):

var objt = func(  );
var div = document.getElementById("json");
stylizeDiv(resp,div);
div = document.getElementById("props");
div.innerHTML="<h4>In object form...</h4><h5>Properties</h5>firstname= "+
        objt.firstname +"<br />lastname="+
        objt.lastname+ "<br />gender="+
        objt.gender+ "<br />country="+
        objt.country;

A variable named objt stores the object literal. The values are pulled from the object with syntax such as objt.firstname. Figure 1-10 shows what the web page looks like after it has received a response.

Figure 1-10. Visualizing JavaScript properties is sweet!


On the Server Side

A Java servlet handles requests for this hack. For those interested in the server activity, here is the doPost( ) method for this code:

protected void doPost(HttpServletRequest httpServletRequest,
                      HttpServletResponse httpServletResponse) throws 
                      ServletException, IOException {
    Map valMap = httpServletRequest.getParameterMap(  );
    StringBuffer body = new StringBuffer("{\\n");

    if(valMap != null) {
        String val=null;
        String key = null;
        Map.Entry me = null;
        Set entries =  valMap.entrySet(  );

        int size = entries.size(  );
        int counter=0;
        for(Iterator iter= entries.iterator(  );iter.hasNext(  );) {
            counter++;
            me=(Map.Entry) iter.next(  );
            val= ((String[])me.getValue(  ))[0];
            key = (String) me.getKey(  );
            if(counter < size) {
                body.append(key).append(":\\"").append(val).append("\\",\\n");
            } else {
                //remove comma for last entry
                body.append(key).append(":\\"").append(val).append("\\"\\n");
            }
        }

    }
    body.append("}");
    AjaxUtil.sendText(httpServletResponse,body.toString(  ));
}

The AjaxUtil class sends the HTTP response with a Content-Type of text/plain; charset=UTF-8. Some web sites have discussed using a Content-Type of application/x-json for JSON, but as of this writing, developers and standards bodies have not yet settled on a standard relating to this matter.

The AjaxUtil class also sets the HTTP response header Cache-Control to no-cache, which tells the browser or user agent not to cache the responses:

response.setHeader("Cache-Control", "no-cache");
               
            


Previous Page
Next Page