Serving JSON

Up until this point we have used two methods of transferring data from the server to the client. Plain text was used in "Hello World" and XML was used in the previous section. Here, we cover JavaScript Object Notation (JSON), which is another popular method for transferring the data. Both have their benefits and there have been extensive debates on the Internet over which is better to use in Ajax applications.

JSON (see http://www.json.org) is described as a "lightweight data-interchange format." This means that you can transfer data without much overhead. In fact, even though the XMLHTTPRequest object only contains built in support for plain text and XML, getting started with a JSON implementation is as simple as one line of code.

The ease of implementation and its lightweight nature make JSON an attractive choice for many PHP developers.

In this section we rewrite the example from the previous "Serving XML" section to use JSON instead. There is very little new code to introduce here, but we do go over the JSON concepts in depth.

Designing the JSON File

JSON is very good at transferring two types of data. These two main types are arrays and objects. In XML, we had a root element that can contain numerous sub elements. These elements were collectively called nodes. In JSON, the two structures closely model the programming language that they are implemented in. Instead of nodes, we have data structures.

Arrays in JSON

The first type of data structure we will study is an array. Arrays follow a simple format of multiple values enclosed in brackets and separated with commas. Let's look at the following example of a JSON array that stores the first 20 prime numbers.

[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71 ]

The data in an array does not need to be composed of numbers. In fact the data does not even have to be off the same type. You can mix and match types. The values can be any one of the following types:

  • Numeric

  • String

  • Object

  • Array

  • Boolean (true or false)

  • Null

The type of data that we are most interested in storing is type object. Objects, like arrays, can store a sequence of values.

Objects in JSON

Unlike arrays, values inside an object have names. Much like in XML we had titles, authors, years, and publishers. Objects are enclosed in a different type of bracket and instead of having just values separated by commas they have name/value pairs. Each name/value pair is the name of the variable followed by a colon then the value. Here is an object that represents this Short Cut.

{
       "Title":"Ajax with PHP 5",
       "Author":"Andrew Curioso",
       "Year":2007,
       "Publisher":"O'Reilly Media"
}

The data does not need to span multiple lines; whitespace was added to make it easier to read by humans. Much like our XML file, this JSON format includes all the information necessary to identify a book in our catalog. Now we are ready to format our data.

The PHP Scripts

In this section, we have two tasks that we need PHP to accomplish. The first is to accept search queries via the GET method. We have already done that in the "Serving XML" section. The second task is to serve up the JSON formatted data, which can be done with one of two methods.

Printing JSON the easy way with the JSON module

As of PHP 5, there is a JSON module installed by default in most PHP configurations. This module does all the heavy lifting for us in just one line of code. Not only that, but the resulting code will be faster than a custom class. That one line is:

json_encode($results);

Printing JSON the harder way with the JSONPrinter Class

On some systems json_encode() isn't available. For those systems we can create a custom class to generate the JSON code. This class is slower than the highly optimized method previously mentioned, however, the output is virtually identical.

Just like with XML, we make a general utility class that contains a static method to print out the JSON for us. When using JSON we won't have a node class like we did with XML, since JSON integrates well with just arrays and associative arrays. With the code we generate a text representation of an array of objects that JavaScript then parses. The method also takes an array of any composition, like we had with the static PrintFromArray() method in the XMLPrinter class:

<?php
class JSONPrinter {
       private static function PrintArray($data,$level)
       {
              echo "[";
              foreach ( $data as $value ) {
                     JSONPrinter::PrintData($value,$level+1);
                     if ( (++$i) != count($data) ) echo ",";
              }
              echo "]";
       }
       private static function PrintObject($data,$level)
       {
              echo "{";
              $i=0;
              foreach ( $data as $key => $value ) {
                     echo ""$key":";
                     JSONPrinter::PrintData($value,$level+1);
                     if ( (++$i) != count($data) ) echo ",";
              }
              echo "}";
       }
       public static function PrintData($data,$level)
       {
              if ( is_array($data) )
              {
                     if ( is_numeric(implode(array_keys($data))) )
                           JSONPrinter::PrintArray($data,$level);
                     else
                           JSONPrinter::PrintObject($data,$level);
              } else {
                     echo """.$data.""";
              }
       }
       public static function PrintFromArray( array $data )
       {
              JSONPrinter::PrintData($data,1);
       }
}
?>

Once again, we have a class that we can freely use in any web application where there is an array in which we would like to format as JSON. (We can save the entire class to a file JSONPrinter.class.php if we want, however, this class won't be used in the following examples. It is here purely for illustration.)

The Main PHP Script

Copy the code below into a file of your choosing and put the file in your web server's path. Once again, don't worry about the JavaScript yet.

<?php
include("BookLoader.class.php");

if ( count( $_GET ) > 0 ) {
       header("Cache-Control: no-cache, must-revalidate");
       header("Content-type: text/plain");

       $results = BookLoader::Search($_GET["title"],$_GET["author"]);
       echo json_encode($results);
} else { ?>
<html>
<head>
       <title>Book search with XML</title>
       <script src="searchJSON1.js"></script>
</head>
<body>
       <form method="post" onSubmit="return SubmitQuery()">
       Title: <input type="text" name="title" id="title"><br>
       Author: <input type="text" name="author" id="author"><br>
       <input type="Submit" value="Search" name="Submit" id="Submit">
       </form>
       <table id="data" border="1">
       <tr>
              <th>Title</th>
              <th>Author</th>
              <th>Year</th>
              <th>Publisher</th>
       </tr>
       <?php
       if ( count($_POST) > 0 )
       {
              $results = BookLoader::Search($_POST["title"],$_POST["author"]);
              foreach ( $results as $book )
              { ?>
       <tr>
              <td><?= $book["Title"] ?></td>
              <td><?= $book["Author"] ?></td>
              <td><?= $book["Year"] ?></td>
              <td><?= $book["Publisher"] ?></td>
       </tr>
              <?
              }
       }
       ?>
       </table>
</body>
</html>
<?php } ?>

Prior to running the script, make sure that BookLoader.class.php and books.csv from the previous sections are in the same directory as your file. Now run your script.

Does it look familiar? It should. It is the exact same application that we made in the "Serving XML" section copied into this section for convenience.

Once again there are three potential paths the script can take:

  • If there isn't any POST data and no GET query, we print the form and an empty search results table.

  • If there is POST data, then we still print the form but we also echo out the search results in a table.

  • If there is a GET query, then we send JSON data back to the client.

Only two lines of code changed. The first change is that the json_encode() method is now used instead of the XMLPrinter.

The second change takes a little bit more explanation. Unlike XML, there isn't any particular MIME type that we should be using for the file. Instead we use "text/plain" just like in the "Hello World" example. The client (web browser) won't recognize the data immediately like it did with XML. We need to write the code to take the JSON data and convert it to a JavaScript usable format.

Parsing JSON Data in JavaScript

Parsing JSON data in JavaScript is incredibly easy. While using XML, the data was automatically converted to a DOM tree, which is different with JSON. The JSON data will be converted to a JavaScript object using this one line of code:

var books = eval(httpObj.responseText);

The books variable is now filled with an array of all the books in our catalog that were returned by the server. Save this JavaScript to searchJSON1.js and place it in the same directory as the PHP:

var httpObj=null;
function NewHTTP()
{
       try { return new XMLHttpRequest(); }
       catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); }
}
function FetchValue(book,field)
{
       try { return eval("book."+field); }
       catch (error) { return "[error]"; }
}
function OnData()
{
       if ( httpObj.readyState==4 ) {
              var errorText = null;
              var table = document.getElementById("data");
              table.deleteRow(1);
              if (httpObj.status==200 ) {
                     try {
                           var books = eval(httpObj.responseText);
                           if ( books == null )
                                  errorText = "Sorry, no results were found.";
                           else for ( var i=0; i<books.length; i++ ) {
                                  var book = books[i];
                                   var row = table.insertRow(i+1);

                                  row.insertCell(0).innerHTML =
                                         FetchValue(book,"Title");
                                  row.insertCell(1).innerHTML =
                                         FetchValue(book,"Author");
                                  row.insertCell(2).innerHTML =
                                         FetchValue(book,"Year");
                                  row.insertCell(3).innerHTML =
                                         FetchValue(book,"Publisher");
                           }
                     } catch (e) {
                           errorText = "Invalid results given.";
                     }
              } else {
                     alert("Error: Could not get search results.");
                     errorText = "Sorry, the search service is unavailable.";
              }
              httpObj = null;
              document.getElementById("Submit").disabled = false;
              if ( errorText != null ) {
                     var row = table.insertRow(1);
                     var cell = row.insertCell(0);
                     cell.colSpan = 4;
                     cell.innerHTML = errorText;
              }
       }
}
function SubmitQuery()
{
       if ( httpObj != null ) return;
       document.getElementById("Submit").disabled = true;
       var table = document.getElementById("data");
       while ( table.rows.length > 1 ) table.deleteRow(1);
       var cell = table.insertRow(1).insertCell(0);
       cell.colSpan = 4;
       cell.innerHTML = "Loading";

       httpObj = NewHTTP();
       var author = escape(document.getElementById("author").value);
       var title  = escape(document.getElementById("title").value);
       httpObj.open("GET","?author="+author+"&title="+title,true);
       httpObj.onreadystatechange = OnData;
       httpObj.send(null);
       return false;
}

This new JavaScript is extremely similar to the XML code used earlier. The SubmitQuery and NewHTTP functions don't change at all. The new code comes in the form of the OnData() and FetchValue() functions.

Now that we saved this file as searchJSON1.js and put it into the same directory as the PHP file we can try it out. If everything is setup correctly, we should be able to visit the page on the web server and search our books.

Handling mal-formatted JSON

Let's first look at the fetch value function. In XML, we had to see if a child node existed and, if it didn't, we returned "[error]." Because the parsed JSON code creates a new object, we only need to retrieve the appropriate value.

Do that by using the function eval() on a string. Let's look at the flow of a function call to FetchValue(book,"title").

Try to evaluate the string "book."+field. This is actually the evaluation of two strings concatenated together. In this example we are getting the title, so what we evaluate is book.Title. Since book is an object, it returns the value of the object's "title" member variable. Then we need to deal with what happens if the member does not exist in the object. In that event, an exception is thrown. Fortunately, we have a try/catch to handle that. In our exception handler, we return the "[error]" string. With this function, we can gracefully handle JSON data that does not contain the fields that we expected. But then we also need to handle data that isn't properly formatted JavaScript Object Notation at all.

To do that, enclose a large chunk of our code inside of a try/catch block. This code is only here to catch one statement, which is the eval. As described earlier, the eval() function evaluates the JSON data and converts it to a JavaScript object. However, if the data is not valid JSON or JavaScript code, then it throws an exception. We then catch that exception and display the appropriate error message.

An alternative way to evaluate JSON

In this Short Cut, we rarely use libraries that are not already preinstalled on the browser or server. However, the one exception is json.js, which can be found at the web site www.json.org. The script provides two key functionalities. One is a safer way to parse JSON data and the other is an easy way to convert a JavaScript object to JSON (the latter of these two is covered in the "Consuming JSON" section).

This library should be used whenever there is a chance that your JSON data may not be safe. The problem with eval() is that it will execute any JavaScript code, not just JSON data! This could lead to a malicious person or web site injecting arbitrary JavaScript code that runs on the client. This code may include vulnerability exploits or may just be used to give the appearance that your site was hacked. For example, a malicious user could execute the code document.getElementById("logo").src="gotcha.jpg" or worse. The json.js script will only parse valid JSON data and will throw an exception otherwise.

It does this by adding various methods to the prototypes of built in JavaScript classes. The method we are covering now is parseJSON(). This method is added to the prototype for the String object. Text by default in JavaScript is not a String object (it is a primitive type) so we must convert the data to a String and then parse it. The code used before to parse the JSON was:

var books = eval(httpObj.responseText);

This code becomes:

var books = new String(httpObj.responseText).parseJSON(null);

Notice the null in the parameters for parseJSON(). This is the "filter" for the parser. The filter (if it is not null) will be executed on every variables parsed. It is sent a key and value pair. The key—the value used as an index for the array—is the name of the variable in the JSON code. The filter can either return the value unchanged or translate/format it somehow. For example, it can be useful if we want to transform a Boolean string into a Boolean object on the fly.

(The script is interesting and well commented. It's a worthwhile read for those of you interested in learning how prototypes work in JavaScript. You can find it at http://www.json.org/json.js.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
98.82.120.188