The final task that we haven't covered yet is parsing JSON on the server side with PHP. There are two parts to this task: generate the JSON request with JavaScript and take that request and return results in JSON format. Just like with the XML format, we need to redesign our results slightly to accommodate multiple queries.
The JSON request needs to include an array of search terms. The terms will consist of an object that has both a title and an author attribute. Here is an example of the JSON request:
[{"title":"PHP", "author":"curioso"}]
Our response will be the same as the responses from before only we now have to include the query in the response as well as the ability to have multiple responses. To refresh our memory, our old response to the query looked like this:
[{ "Title":"Ajax with PHP 5", "Author":"Andrew Curioso", "Year":2007, "Publisher":"O'Reilly Media" }]
When we add our query encapsulation information it looks like this:
[{ "query":{ title:"PHP", author:"Curioso" }, "Books":[{ "Title":"Ajax with PHP 5", "Author":"Andrew Curioso", "Year":2007, "Publisher":"O'Reilly Media" }] }]
The new response includes an array of objects that each contains two members. One of the members is a query object just like the one we send to the server and the other is an array of all the books returned in the result.
We use almost the same PHP file as with the previous example on consuming XML here. One thing that needs to change is the JavaScript inclusion. Instead of including searchXML2.js we include searchJSON2.js. The new file generates the JSON query as well as parses the results.
Here, we need to use json.js. Sure, we can write our own generator like we did with XML, but there is a perfectly good and free (i.e., in public domain) solution for us to use. (Download it here: http://www.json.org.)
The method that we use this time is
toJSONString()
. Using prototypes this method is added
to several JavaScript classes automatically when the file is included.
From the documentation found at the beginning of json.js (April 2007),
we can see that these classes are as follows:
Array
Boolean
Date
Number
Object
String
These six classes can now be easily converted to their JSON
representation using the aforementioned function. For example, let's say
we have the variable foo
, which is of type
Object
. We can call
foo.toJSONString()
, which provides us with a JSON
representation of foo
. This is assuming, of course,
that we previously included the json.js file.
To use this method, we must first generate an object to parse. We can generate the object in real-time by accessing the members as if they already existed. Let's examine the following example:
var bookQuery = new Object(); bookQuery.title = "PHP"; bookQuery.author = "Curioso"; var JSONText = bookQuery.toJSONString());
There are two ways to produce the JSON data: create an object and then assign the members or generate the JSON code directly using strings. This may seem more appropriate in some cases:
var title = "PHP"; var author = "Curioso"; var JSONText = "{"title":""+title+"","author":""+author+""}";
One problem with this second method is that it can be extremely
difficult to read and debug for large objects. The other problem, and
this one is more important, is security. The
toJSONString()
method takes care to ensure that
strings are escaped properly. Not doing this could result in arbitrary
JavaScript being injected into the data in much the same way that SQL
injection is done. With proper care the string can be manual escaped,
however toJSONString()
takes care of the messy part
for us.
Now go ahead and edit the PHP file. This time, include searchJSON2.js instead of searchJSON1.js. Include json.js as well (from the http://www.json.org site). Since the former script (searchJSON2.js) doesn't exist yet, we'll create it. Fortunately, we can reuse much of the code from our searchJSON1.js and searchXML2.js files. We copy and paste the function from the appropriate files (see Table 2).
Table 2. Functions from files
searchJSON1.js | searchXML2.js |
---|---|
|
|
With those four functions out of the way, there are three left to
write. BuildQuery()
will build our JSON query in much
the same way we did with the XML equivalent. OnData()
will parse the JSON data. SubmitAjaxQuery()
needs one
small change.
Copy and paste SubmitAjaxQuery()
from
searchXML2.js just as we did before. We only need to make two changes.
First, we change the content type to be
"application/x-www-form-urlencoded" instead of "text/xml." Now we need
to change the way that send()
is called.
Instead of sending just the method BuildQuery()
we need to change the send line to say:
httpObj.send("JSONQuery="+escape(BuildQuery()));
This time around we escape the query. When posting multipart form
data, the information should be URL encoded. We are done with that
function. Now that we have that function taken care of we can tackle the
BuildQuery()
function. To accomplish this, use the
toJSONString()
function discussed earlier.
function BuildQuery() { var table = document.getElementById("formTable"); var rows= table.getElementsByTagName("tr"); var queries = new Array(rows.length-2); for ( var i=1; i<rows.length-1; i++ ) { queries[i-1] = new Object(); queries[i-1].title = document.getElementById(rows[i].id+"title").value; queries[i-1].author = document.getElementById(rows[i].id+"author") .value; } return queries.toJSONString(); }
Finally, write the OnData()
event
handler.
function OnData() { if ( httpObj.readyState==4 ) { var errorText = null; var table = document.getElementById("data"); table.deleteRow(1); if (httpObj.status==200 ) { try { var queries = eval(httpObj.responseText); var curRow = 1; if ( queries == null ) errorText = "Sorry, no queries were found."; else for ( var i=0; i<queries.length; i++ ) { var row = table.insertRow(curRow); curRow++; row.innerHTML = "<td colspan=4>Search for: "+ FetchValue(queries[i].query,"title") + " " + FetchValue(queries[i].query,"author")+ "</td>"; for ( var j=0; j<queries[i].books.length; j++ ) { row = table.insertRow(curRow); curRow++; row.insertCell(0).innerHTML = FetchValue(queries[i].books[j],"Title"); row.insertCell(1).innerHTML = FetchValue(queries[i].books[j],"Author"); row.insertCell(2).innerHTML = FetchValue(queries[i].books[j],"Year"); row.insertCell(3).innerHTML = FetchValue(queries[i].books[j],"Publisher"); } } } catch (e) { errorText = "Invalid results given."+e.message; } } else { alert("Error: Could not get search results."); errorText = "Sorry, the search service is unavailable."; } httpObj = null; document.getElementById("Submit").disabled = false; document.getElementById("Add").disabled = false; if ( errorText != null ) { var row = table.insertRow(1); var cell = row.insertCell(0); cell.colSpan = 4; cell.innerHTML = errorText; } } }
The only difference between this OnData()
and
the OnData()
in the searchJSON1.js script is that we
must now traverse the additional "query" tag to print out the section
headers.
Rather than restate the entire file, this section will cover only the part that changed. Open up the file we created in the "Consuming XML" section. Now scroll down until you find the line that says:
if ( strpos($HTTP_RAW_POST_DATA,"<request>") !== false ) {
Replace the code from (and including) this line down to the
else
statement. Instead of parsing it as XML, we
interpret the JSON data. Just to refresh your memory, we use
json_encode()
to generate the response.
Appropriately, there is also a json_decode()
function
that undoes the encoding. Use both functions in this script.
if ( isset( $_POST["JSONQuery"] ) ) { try { $data = json_decode(stripslashes($_POST["JSONQuery"])); if ( !is_array($data) ) { throw new Exception("JSON Error"); } $response = array(); foreach ( $data as $v ) { if ( !isset($v->title) || !isset($v->author) ) throw new Exception("Invalid Query"); $response[] = array( "query" => array( "title" => $v->title, "author" => $v->author ), "books" => BookLoader::Search($v->title, $v->author) ); } echo json_encode($response); } catch ( Exception $e ) { header( "HTML/1.0 500 Internal server error" ); echo "Error: ".$e->getMessage(); exit; } }
This code is similar to the XML version. We generate an array
that, when serialized, provides the appropriate JSON output. The code to
initialize $HTTP_RAW_POST_DATA
can now be deleted
since we no longer use it.
In this section, we modified our XML consumer to consume and
produce JSON instead. We also took full advantage of both
json_encode()
and json_decode()
as
well as the json.js class. Even though consuming JSON on the server is
not yet common, it can be a lightweight way to transmit complex
data.
98.82.120.188