JSON and the Dynamic Script Tag: Easy, XML-less Web Services for JavaScript
December 21, 2005
Making requests to third-party web services from an AJAX application is a pain, but new web services that offer the option of returning JSON (JavaScript Object Notation) instead of XML can
provide significant relief. In fact, if you make web services requests using the dynamic
script
tag approach -- and the web service lets you specify a JavaScript
callback function -- you can have unfettered access to the web service in a seamless,
cross-domain, cross-browser fashion.
Here's what you need to try out this dynamic script
tag request:
-
My
JSONscriptRequest
class - Access to a web service that returns JSON and lets you specify a callback function
To create my class, I distilled a lot of existing information, and then adapted it to fit no. 2 above. Until recently, finding a web service that returns JSON and lets you specify a callback function was, well, darn near impossible unless you wrote one yourself. Fortunately, Yahoo recently has offered the option on many of their REST-ish web services. Notably, their many search web services, and their geocoding, map image, and traffic web services, now can return JSON wrapped in a callback function.
The KISS Factor
Compared to using the XMLHttpRequest
object and a proxy, this stuff is easy.
The JSONscriptRequest
class does the messy work of creating the
script
tag dynamically which makes the actual web service request. For a
quick example, I'll do some geocoding, turning a zip code -- in this case zip code
94107 --
into a latitude/longitude pair, using Yahoo's Geocoding Web Service.
<html> <body> // Include the JSONscriptRequest class <script type="text/javascript" src="jsr_class.js"> </script> <script type="text/javascript"> // Define the callback function function getGeo(jsonData) { alert('Latitude = ' + jsonData.ResultSet.Result[0].Latitude + ' Longitude = ' + jsonData.ResultSet.Result[0].Longitude); bObj.removeScriptTag(); } // The web service call var req = 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&output=json&callback=getGeo&location=94107'; // Create a new request object bObj = new JSONscriptRequest(req); // Build the dynamic script tag bObj.buildScriptTag(); // Add the script tag to the page bObj.addScriptTag(); </script> </body> </html>
Running this application makes a request to Yahoo's Geocode web service and yields the following alert box which displays the latitude and longitude of the zip code 94107.
Figure 1. Alert box
The web service request -- the req
variable in the script above -- specifies
that the web service should return JSON-encoded data (output=json
) and that the
data should be wrapped in a callback function named getGeo
(callback=getGeo
). You can cut and paste that URL into your browser to see
the output of the web service. The output looks like this (or click here):
getGeo({"ResultSet":{"Result":[{"precision":"zip","Latitude":"37.7668","Longitude":"-122.3959","Address":"","City":"SAN FRANCISCO","State":"CA","Zip":"94107","Country":"US"}]}});
That's a valid JavaScript statement, so it can be the target of a script
tag
that returns JavaScript (raw JSON data, without the callback function, is not a valid
JavaScript statement, so it will fail to load if it is the target of a script
tag). For comparison, look at the XML version of this call here.
The buildScriptTag
method of the JSONscriptRequest
object builds
a script
tag that looks like this:
<script src="getGeo({"ResultSet":{"Result":[{"precision":"zip","Latitude":"37.7668","Longitude":"-122.3959","Address":"","City":"SAN FRANCISCO","State":"CA","Zip":"94107","Country":"US"}]}});" type="text/javascript">
To actually execute the web service request, the script
tag has to be added to
the page. The addScriptTag
method attaches the script
tag to the
HTML page which is already loaded in your browser window. That action causes the
getGeo
function to be called and the JSON-encoded data to be passed to the
getGeo
function. Now comes the magic part of the script -- it's a side effect
of using JSON-encoded data instead of XML. When a string of JSON-encoded data is used
as an
argument to a JavaScript function, the JavaScript interpreter automatically turns
the JSON
into a JavaScript object. Essentially, the parsing step is done automatically and
you can
reference the data immediately:
alert('Latitude = ' + jsonData.ResultSet.Result[0].Latitude + ' Longitude = ' + jsonData.ResultSet.Result[0].Longitude);
Pros and Cons
The HTML script
tag is the last frontier of unfettered access for
browser-based applications. Depending on your viewpoint, it's either a gaping security
hole,
or a tool to make rich clients even richer. Its most common use, though, is by internet
advertisers who use it to pull their colorful ads into your web page. For the average
AJAX,
or AJAJ, (Asynchronous Javascript And JSON) developer, the dynamic script
tag
approach can make life easier in certain scenarios, but, feature-wise, the
XMLHttpRequest
object is still a more reliable, flexible, and secure request
mechanism (see Table 1).
XmlHttpRequest
|
Dynamic script Tag |
|
---|---|---|
Cross-browser compatible? | No | Yes |
Cross-domain browser security enforced? | Yes | No |
Can receive HTTP status codes? | Yes | No (fails on any HTTP status other than 200) |
Supports HTTP GET and POST ? |
Yes | No (GET only) |
Can send/receive HTTP headers? | Yes | No |
Can receive XML? | Yes | Yes (but only embedded in a JavaScript statement) |
Can receive JSON? | Yes | Yes (but only embedded in a JavaScript statement) |
Offers synchronous and asynchronous calls? | Yes | No (asynchronous only) |
The script
tag's main advantages are that it is not bound by the web browser's
cross-domain security restrictions and that it runs identically on more web browsers
than
XMLHttpRequest
. Further, if your web service happens to offer JSON output and
a callback function, you can nimbly access web services from within your JavaScript
applications without having to parse the returned data.
XMLHttpRequest
is available in all the latest browsers, but IE's
implementation is somewhat different and requires a compatibility layer (such as Sarissa) to make it work across all
browsers. XMLHttpRequest
can receive raw JSON as well as XML, plain text, and
HTML -- in fact, it handles any non-binary data easily. It also can send and receive
individual HTTP headers, can do both HTTP GET
s and POST
s, and
supports both synchronous and asynchronous calls. In short, if there's a problem with
your
web services request, such as invalid XML or a server error, the XMLHttpRequest
gives programmers tools to handle the situation.
In contrast, the script
tag offers few of the XMLHttpRequest
capabilities. The most notable downside is that it cannot handle errors gracefully.
If the
web service returns an invalid JavaScript statement to the script
tag, a
JavaScript error will be generated. If the web service returns invalid JSON wrapped
inside a
callback function, a JavaScript error will be returned when the invalid JSON data
is passed
to the callback function. If your web service returns an HTTP return code other than
200
(successful), then the script
tag will silently fail.
To be fair, script
tag requests don't actually work exactly the same way
across all browsers. The event handling -- how you wait for the tag to load -- is
a bit
different. Technically, dynamically generated script
tags load asynchronously,
but, there is no reliable, cross-platform way to wait for a script
tag to load.
Microsoft's IE uses one method described here, while the HTML 4.0 specification suggests the onload event
handler, though it doesn't seem to work across all browsers.
The security issues surrounding the script
tag cannot be ignored. Malicious
scripts downloaded into your browser run with the same authority as other scripts
in your
page, so a villainous script can steal your cookies or misuse any authorization that
you may
have with a server. These villainous scripts can more easily send and receive stolen
data
using the script
tag. For this reason, applications using the dynamic
script
tag approach need to be carefully vetted.
With all of these drawbacks, it is unlikely that programmers will be flocking to implement
or re-implement web services requests using script
tags. Still, it's well
suited for scripting applications where non-critical data needs to be retrieved from
third-party sources easily.