Menu

JSON and the Dynamic Script Tag: Easy, XML-less Web Services for JavaScript

December 21, 2005

Jason Levitt

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:

  1. My JSONscriptRequest class
  2. 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.

alert box
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).

Table 1. XMLHttpRequest Compared to the Dynamic script Tag

  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 GETs and POSTs, 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.