August 15, 2009

Serving JSON data the JSONP way with Groovlets

If we want to serve JSON data and want it to be cross-domain accessible we can implement JSONP. This means that if we have a Groovlet with JSON output and want users to be able to access it with an AJAX request from the web browser we must implment JSONP. JSONP isn't difficult at all, but if we implement it our Groovlet is much more useful, because AJAX requests can be made from the web browser to our Groovlet.

The normal browser security model only allows calls to be made to the same domain as the web page from which the calls are made. One of the solutions to overcome this is the use of the script tag to load data. JSONP uses this method and basically let's the client decide on a bit of text to prepend the JSON data and enclose it in parentheses. This way our JSON data is encapsulated in a Javascript method and is valid to be loaded by the script element!

The following code shows a simple JSON data structure:

{ "title" : "Simple JSON Data", 
  "items" : [ 
    { "source" : "document", "author" : "mrhaki" }
    { "source" : "web", "author" : "unknown"}

If the client decides to use the text jsontest19201 to make it JSONP we get:

jsontest19201({ "title" : "Simple JSON Data", 
  "items" : [ 
    { "source" : "document", "author" : "mrhaki" }
    { "source" : "web", "author" : "unknown"}

Okay, so what do we need to have this in our Groovlet? The request for the Groovlet needs to be extended with a query parameter. The value of this query parameter is the text the user decided on to encapsulate the JSON data in. We will use the query parameter callback or jsonp to get the text and prepend it to the JSON data (notice we use Json-lib to create the JSON data):

import net.sf.json.JSONObject


def jsonOutput = JSONObject.fromObject([title: 'Simple JSON data'])
jsonOutput.accumulate('items', [source: 'document', author: 'mrhaki'])
jsonOutput.accumulate('items', [source: 'web', author: 'unknown'])

// Check query parameters callback or jsonp (just wanted to show off
// the Elvis operator - so we have two query parameters)
def jsonp = params.callback ?: params.jsonp
if (jsonp) print jsonp + '('
if (jsonp) print ')'

We deploy this Groovlet to our server. For this blog post I've uploaded the Groovlet to Google App Engine. The complete URL is http://mrhakis.appspot.com/jsonpsample.groovy. So if we get this URL without any query parameters we get:

{"title":"Simple JSON data","items":[{"source":"document","author":"mrhaki"},{"source":"web","author":"unknown"}]}

Now we get this URL again but append the query parameter callback=jsontest90210 (http://mrhakis.appspot.com/jsonpsample.groovy?callback=jsontest90210) and get the following output:

jsontest90210({"title":"Simple JSON data","items":[{"source":"document","author":"mrhaki"},{"source":"web","author":"unknown"}]})

We would have gotten the same result if we used http://mrhakis.appspot.com/jsonpsample.groovy?jsonp=jsontest90210. The good thing is user's can now use for example jQuery's getJSON() method to get the results from our Groovlet from any web page served on any domain.

The following is generated with jQuery.getJSON() and the following code:

$(document).ready(function() {
  $.getJSON('http://mrhakis.appspot.com/jsonpsample.groovy?callback=?', function(data) {
    $.each(data.items, function(i, item) {
      $("<p/>").text("json says: " + item.source + " - " + item.author).appendTo("#jsonsampleitems");

JSONP output: