Dealing with JSON



XML is used to represent structured data, but dealing with XML DOM is complex. In a web application, data are not always so complex that we really need XML. If that's so, JSON may be used for data interchange.

JSON stands for "JavaScript Object Notation". It is based on a subset of JavaScript object literals. You can find complete description of JSON structures in http://www.json.org. In general, JSON is similar to object literals. The primary differences include:
  • A name is a double-quoted Unicode string with backslash escaping.
  • A value can be a string in double quotes, or a number, true, false, null, an object or an array.
  • Several JavaScript data types, such as Date, Error, RegExp, and Function, are not included.
For example, the following is an object literal:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3} ]
};

The following is its JSON representation:
var json = '{"name": "Justin", "age": 35, "childs": [{"name": "hamimi", "age": 3}]}';

Format the above for readability:
{
    "name":"Justin",
    "age":35,
    "childs":[
        {
            "name":"hamimi",
            "age":3
        }
    ]
}

You can send a JSON string to the server. Creating a JSON string is easy. Firefox 3.1, Internet Explorer and newer browsers have a simple JSON library. Creating a JSON string from an object just needs the JSON.stringify function. For example:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3} ]
};
var json = JSON.stringify(obj);

Then, you can get a JSON string as shown previously. Sending JSON through an asynchronous object can be as follows:
var request = xhr(); // xhr() returns an asyhcnronous object
request.onreadystatechange = handleStateChange; // handleStateChange is a function
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/json');
request.send(json);

It's recommended to set the request header 'Content-Type' as 'application/json'. Of course, the server has to parse the JSON string. You don't need to write one by yourself. The site http://www.json.org provides JSON parsers with different language implementations to help you with parsing a JSON string.

When a server sends a JSON string, you can use the eval function to convert the JSON string into a JavaScript object. For example:
var obj = eval(json);

The eval function, however, can also evaluate JavaScript expressions or execute JavaScript statements. If you just want to parse a JSON string into an object, the JSON.parse function is recommended. For example:
var obj = JSON.parse(json);

For browsers before Firefox 3.1, Internet Explorer 8, or those without built-in JSON supports, you can download json2.js from http://www.json.org (and other files. see the README for details). Embed the script file in your page; you'll have the methods mentioned previously:
<script type="text/javascript" src="json2.js">

If a browser has built-in JSON supports, json2.js does nothing. The following is a demonstration of using JSON. It provides a list of suggested terms. (The server isn't really a search engine.)  If there're suggested terms, a JSON-formatted string array is sent, such as '["caterpillar", "ceo"]'. (The server's suggested terms include "caterpillar", "car", "ceo", "c++", "justin", "java", and "javascript".) You have to know the exact position of the input box to align suggested terms under it. You can see how to do this in the third example of Element Positions.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <style type="text/css">
            div {
                color: #ffffff;
                background-color: #ff0000;
                border-width: 1px;
                border-color: black;
                border-style: solid;
                position: absolute;
            }        
        </style>
        <script type="text/javascript" src="js/json2.js"></script>
        <script type="text/javascript">
            window.onload = function() {
                function offset(element) {
                    var x = 0;
                    var y = 0;
                    while(element) {
                        x += element.offsetLeft;
                        y += element.offsetTop;
                        element = element.offsetParent;
                    }
                    return { 
                        x: x, 
                        y: y, 
                        toString: function() {
                            return '(' + this.x + ', ' + this.y + ')';
                        }
                    };
                }
                var xhr = window.XMLHttpRequest && 
                      (window.location.protocol !== 'file:' 
                          || !window.ActiveXObject) ?
                       function() {
                           return new XMLHttpRequest();
                       } :
                       function() {
                          try {
                             return new ActiveXObject('Microsoft.XMLHTTP');
                          } catch(e) {
                             throw new Error('XMLHttpRequest not supported');
                          }
                       };
                
                function param(obj) {
                    var pairs = [];
                    for(var name in obj) {
                        var pair = encodeURIComponent(name) + '=' + 
                                   encodeURIComponent(obj[name]);
                        pairs.push(pair.replace('/%20/g', '+'));
                    }
                    return pairs.join('&');
                }
                
                function ajax(option) {
                    option.type = option.type || 'GET';
                    option.header = option.header || {
                      'Content-Type':'application/x-www-form-urlencoded'};
                    option.callback = option.callback || function() {};
                    
                    if(!option.url) {
                        return;
                    }
                    
                    var request = xhr();
                    request.onreadystatechange = function() {
                        option.callback.call(request, request);
                    };
                    
                    var body = null;
                    var url = option.url;
                    if(option.data) {
                        if(option.type === 'POST') {
                            body = param(option.data);
                        }
                        else {
                            url = option.url + '?' + param(option.data) 
                                     + '&time=' + new Date().getTime();
                        }
                    }
                    
                    request.open(option.type, url);
                    for(var name in option.header) {
                        request.setRequestHeader(
                                name, option.header[name]);
                    }
                    request.send(body);
                }
                var search = document.getElementById('search');
                search.onkeyup = function() {
                    var divs = document.getElementsByTagName('div');
                    for(var i = 0; i < divs.length; i++) {
                        document.body.removeChild(divs[i]);
                    }
                    if(search.value === '') {
                        return;
                    }
                    ajax({
                        url     : 'JSON-1.php',
                        data    : {keyword : search.value},
                        callback: function(request) {
                            if(request.readyState === 4) {
                                if(request.status === 200) {
                                    var keywords = JSON.parse(
                                            request.responseText);
                                    if(keywords.length !== 0) {
                                        var innerHTML = '';
                                        for(var i = 0; 
                                              i < keywords.length; i++) {
                                            innerHTML += 
                                                (keywords[i] + '<br>');
                                        }
                                        var div = 
                                              document.createElement('div');
                                        div.innerHTML = innerHTML;
                                        var xy = offset(search);
                                        div.style.left = xy.x + 'px';
                                        div.style.top = 
                                             xy.y + search.offsetHeight + 'px';
                                        div.style.width = 
                                             search.offsetWidth + 'px';
                                        document.body.appendChild(div);
                                    }
                                    
                                }
                            }
                        }
                    });
                };
            };
        </script>        
    </head>
    <body>
        <hr>
        Search: <input id="search" type="text">
    </body>
</html>

This example doesn't consider users' typing speed, so it'll send a request every one typing. To avoid problems of frequent requests due to users' rapid typing, you may try a time period setting, such as send a request every one second.

In addition, for each suggested terms, you may provide a click event handler. When a user clicks a term, set its text in the input box, and then remove the div element. This is a practice left for you.