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.
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.
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.
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.
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.
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);
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.)
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 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.
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.
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.)
98.82.120.188