A couple weeks a go someone mentioned to me the idea of building JSON services with XFire/CXF. The thought hadn’t really occurred to me, but it seemed like a good one. If you’re already building a REST or SOAP web service, why not allow JSON as a format? JSON has a couple advantages. First, it doesn’t have to be as verbose as XML. XML requires both an end and start tag. JSON has brackets. Second, it is easier to work with for Javascript developers.
After about two evening hacking sessions, I came up with Jettison - as in let’s Jettison our XML and use JSON! Jettison is a StAX implementation that reads and writes JSON. You simply tell CXF about the new StAX implementation and what Content-Type you’re serving. Combine this with the CXF REST support and we have an instant JSON REST web services!
Currently Jettison implements two ways of mapping XML concepts to JSON - the BadgerFish convention and also a “mapped” convention. The later is my favorite as BadgerFish seems very verbose and hard to work with for javascript developers. The mapped convention allows you to map XML namespaces to prefixes for JSON elements. For instance, if we map “http://acme.com” to the “acme” prefix, this XML:
<price xmlns="http://acme.com">10.00</price>
Can be turned into this JSON:
{ "acme.price" : { "10.00" }
This assumes that there is some out of band communication of the JSON structure, however for many (most?) situations this is OK as it is being consumed by javascript anyway. And the less parsing you have to do there the better!
Lets take, the customer service again from my previous example and look at one of the operations:
@Get
@HttpResource(location="/customers")
@WebResult(name="Customers")
Customers getCustomers();
Our operation here is pretty simple. We get a request on /customers and we want to return a List of customers. You may wonder why we aren’t returning a List directly. While CXF supports that, it generates ugly XML infoset, so I created something a bit more visually pleasing.
Setting up the JSON parser on the endpoint is fairly simple. We need to create a map of the how to map XML namespaces to prefixes for our JSON elements. Then we’ll create a StAX XMLInputFactory and XMLOutputFactory for the service:
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setServiceClass(CustomerService.class);
...
HashMap<String, String> nstojns = new HashMap<String,String>
nstojns.put("http://customer.acme.com", "acme");
MappedXMLInputFactory xif = new MappedXMLInputFactory(nstojns);
properties.put(XMLInputFactory.class.getName(), xif);
MappedXMLOutputFactory xof = new MappedXMLOutputFactory(nstojns);
properties.put(XMLOutputFactory.class.getName(), xof);
sf.setProperties(properties);
sf.create();
Now if we do a GET on http://localhost:8080/json/customers, we get JSON back:
{"acme.Customers":{"acme.Customer":{"acme.id":"123","acme.name":"Dan Diephouse"}}}
We also have an addCustomers operation:
@Post
@HttpResource(location = "/customers")
long addCustomer(@WebParam(name = "Customer") Customer c);
Now we can post JSON to our services:
$ cat add.json
{
"acme.Customer" : {
"acme.name" : "Ricky Bobby"
}}
$ wget --post-file=add.json http://localhost:8080/json/customers
And then a GET on http://localhost:8080/json/customers will return:
{"acme.Customers":{"acme.Customer":
[{"acme.id":"123","acme.name":"Dan Diephouse"}, {"acme.id":"1","acme.name":"Ricky Bobby"}]
]}}
If you want to explore this example more, I’ve put together a demo which can be downloaded here. It publishes a set of REST JSON, REST XML, and SOAP XML services complete an HTML/Javascript demo and example files which you can use with wget. I’ll integrate this into the CXF distribution sometime soon, but currently the CXF samples all use ant, and I need to figure out whether I want to switch to ant or find a Maven strategy for the samples.