JSONWithPadding Callbacks: JSON, XML, String and the GenericEntity

 Introduction

I have recently been exploring JSONWithPadding and its’ resolution of types.  JSONWithPadding is defined by Jersey as follows:

An entity supporting JSON with Padding (JSONP).
 
  If an instance is returned by a resource method and the most acceptable
  media type is one of application/javascript, application/x-javascript,
  text/ecmascript, application/ecmascript or text/jscript then the object
  that is contained by the instance is serialized as JSON (if supported, using
  the application/json media type) and the result is wrapped around a
  JavaScript callback function, whose name by default is "callback". Otherwise,
  the object is serialized directly according to the most acceptable media type.
  This means that an instance can be used to produce the media types
  application/json, application/xml in addition to application/x-javascript.

In our case we will be return the media type application/x-javascript.  We have setup paths to URI that invoke resource methods which execute JSONWithPadding with parameters of different types.  The features that we are interested in are:

  1. The call back mechanism JSONWithPadding implements.
  2. The resolution of the response types into JSON.

Discussion

1. The call back mechanism JSONWithPadding implements.

We mark the resource class or method with the annotation. x-javascript did mean experimental javascript at one time.  I think here it means extraordinary.  In any event the response to our request will be a script item that will be executed upon being received by the client.

@Produces("application/x-javascript")
(or application/javascript, text/ecmascript, application/ecmascript or text/jscript)

The resource method will simple return the JSONWithPadding object:

@GET
@Path("/string")
public JSONWithPadding getString(@QueryParam("callback") String callback) {
	// Some code
	return new JSONWithPadding(objectOfIntereset,callback);
}

 The JSONWithPadding will now do a couple of things for us.  It will convert the objectOfInterest into a json object using one of Jersey’s providers (given that there is an approprate conversion provider available) and it will associate the return information with the call back.

The request made by the HTML page is as follows.  It is a clever implementation that executes the REST service(s) on loading the html page.  Below we can see the uri /jsonp/string and the call back script function stringCallback.

<script type="text/javascript"; src="/json/webresources/jsonp/string?callback=stringCallback"></script>

The script that is being used as the call back is stringCallback. It is simply defined as:

function stringCallback(stringObj) {
	document.writeln("=stringCallback result=");
	document.writeln(stringObj);
}

2. The resolution of the response types into JSON.

In the accompanying example code we have implemented four resource methods which utilize the JSONWithPadding class.  In each of the cases we pass a different object type as a parameter: String, JSONObject, Person and a generic collection of Person objects.

return new JSONWithPadding(datatypeOfInterest,callback);

When the objects get serialized to json there is a process that looks at the type to marshal that type of class.  JSONObject and String both get marshalled in fairly straight forward way.  Person, because it has been marked up for JAXB, is available to seralized to JSON through the JAXBContext and .  There is, however, some additional concerns when we attempt to marshall a collection of type Person.  Person is a complex class which would not serialize to  JSON directly.  The JAXB allows for the conversion. There is a provider which marshalls from a JAXB object to JSON.  The JSONWithPadding utilizes these providers through the JSONJAXBElementProvider.  This is all happening behind the scenes.

 

There is an interesting twist when we present a list of persons as the parameter: List<Person> persons.   When we present persons collection directly the class is unable to resolve the generic type of the collection.  This is due to type erasure.  Type erasure enables Java applications that use generics whilst maintaining binary compatibility with Java libraries and applications that were created before generics. This means if we use the persons collection directly the collection’s class type information will not be available for the transformation of the collection. 

The way around this problem is through the use of the GenericEntity class. The GenericEntity class wraps the collection maintaining its generic type.

@GET
@Path("/jaxbCollection")
public JSONWithPadding getJaxbCollection(@QueryParam("callback") String callback)
		throws Exception {
	return new JSONWithPadding(new GenericEntity<List<Person>>(persons) {}, callback);
}

This anonymous class created with GenericEntity will make the type available for the MessageBodyWriter fascilitating the conversion of our collection or Person objects into JSON objects.

The Implementation

The PersonResource class:

package com.persistent.resource;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.GenericEntity;

import org.codehaus.jettison.json.JSONObject;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.persistent.model.Person;
import com.sun.jersey.api.json.JSONWithPadding;

@Component
@Path("/jsonp")
@Produces("application/x-javascript")
@Scope("request")
public class PersonResource {
	private static final List<Person> persons = new ArrayList<Person>();
	static {
		persons.add(new Person(1,"Authur Koestler",99));
		persons.add(new Person(1,"Karl Popper",44));
		persons.add(new Person(1,"Stephen Mumford",79));
	}

	@GET
	@Path("/string")
	public JSONWithPadding getString(@QueryParam("callback") String callback) {
		StringBuffer buffer = new StringBuffer("'");
		for (Person person : persons) {
			buffer.append(person.getName()).append(": ").append(person.getAge());
		}
		buffer.append("'");
		System.out.println(buffer.toString());
		return new JSONWithPadding(buffer.toString(),callback);
	}

	@GET
	@Path("/json")
	public JSONWithPadding getJson(@QueryParam("callback") String callback)
			throws Exception {
		JSONObject jsonPerson = new JSONObject();

		for (Person person : persons) {
			JSONObject obj = new JSONObject();
			obj.put("id", person.getId());
			obj.put("name", person.getName());
			obj.put("age", person.getAge());
			jsonPerson.accumulate("persons", obj);
		}
		System.out.println(jsonPerson.toString());
		return new JSONWithPadding(jsonPerson, callback);
	}

	@GET
	@Path("/jaxbObject")
	public JSONWithPadding getJaxbObject(@QueryParam("callback") String callback)
			throws Exception {
		return new JSONWithPadding(persons.get(0), callback);
	}

	@GET
	@Path("/jaxbCollection")
	public JSONWithPadding getJaxbCollection(@QueryParam("callback") String callback)
			throws Exception {
		return new JSONWithPadding(new GenericEntity<List<Person>>(persons) {}, callback);
	}

}

The HTML client:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSONWithPadding Callbacks: JSON, XML, STRING and the GenericEntity</title>
</head>
<body>
<script type="text/javascript">
	function stringCallback(stringObj) {
		document
				.writeln("<p>============== stringCallback result =========</p>");
		document.writeln(stringObj);
	}
	function jsonCallback(jsonObj) {
		document.writeln("<p>============== jsonCallback result =========</p>");
		writeArray(jsonObj.persons);
	}

	function jaxbObjectCallback(jaxbObject) {
		document.writeln("<p>============== jaxbObjectCallback result =========</p>");
		writePerson(jaxbObject);
	}

	function jaxbCollectionCallback(jaxbObj) {
		document.writeln("<p>============== jaxbCollectionCallback result =========</p>");
		writeArray(jaxbObj.person);
	}

	function writeArray(info) {
		for ( var int = 0; int < info.length; int++) {
			writePerson(info[int]);
		}
	}

	function writePerson(person){
		document.writeln(person.name+": "+person.age);
	}

</script>
<script type="text/javascript"
	src="/json/webresources/jsonp/string?callback=stringCallback"></script>
<script type="text/javascript"
	src="/json/webresources/jsonp/json?callback=jsonCallback"></script>
<script type="text/javascript"
	src="/json/webresources/jsonp/jaxbObject?callback=jaxbObjectCallback"></script>
<script type="text/javascript"
	src="/json/webresources/jsonp/jaxbCollection?callback=jaxbCollectionCallback"></script>

<body>
</body>
</html>

Output of HTML Page

Upon successful execution the html page will produce:

============== stringCallback result =========
Authur Koestler: 99Karl Popper: 44Stephen Mumford: 79

============== jsonCallback result =========
Authur Koestler: 99 Karl Popper: 44 Stephen Mumford: 79

============== jaxbObjectCallback result =========
Authur Koestler: 99

============== jaxbCollectionCallback result =========
Authur Koestler: 99 Karl Popper: 44 Stephen Mumford: 79

 

Articles of Interest

The Source Code

The source code is here: json.zip

About The Author

David Sells is a computer consultant in Toronto, Ontario specializing in Java Enterprise Development. His clients have included companies such as: IBM, Goldman Sachs, Merrill Lynch and Deutsche Bank.

Sun Certified Enterprise Architect for the Java Platform, Enterprise Edition 5

Contact: david@persistentdesigns.com

 

 

1 comment to JSONWithPadding Callbacks: JSON, XML, String and the GenericEntity

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>