Skip to content

Using JAXB

Christoph Beck edited this page Sep 18, 2011 · 21 revisions

StAXON makes JSON look like XML on the Java side. One benefit from this is that you can integrate JSON with powerful XML-related technologies for free. The Java XML Binding API is a widely used standard and can be easily adopted for use with JSON.

Twitter Example

This sample has been inspired by JSON Binding with EclipseLink MOXy - Twitter Example by Blaise Doughan.

Model

First of all, we need a JAXB-annotated model.

Our model classes only contain a small subset of the properties provided by the Twitter Search API. The root element class will be SearchResults, which contains list of Result objects. The DateAdapter class is used to parse and format java.util.Date values.

SearchResults.java

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class SearchResults {
	private String query;
	private float completedIn;
	private List<Result> results;

	public String getQuery() {
		return query;
	}
	public void setQuery(String query) {
		this.query = query;
	}

	@XmlElement(name = "completed_in")
	public float getCompletedIn() {
		return completedIn;
	}
	public void setCompletedIn(float completedIn) {
		this.completedIn = completedIn;
	}

	public List<Result> getResults() {
		return results;
	}
	public void setResults(List<Result> results) {
		this.results = results;
	}
}

Result.java

import java.util.Date;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

public class Result {
	private Date createdAt;
	private String fromUser;
	private String text;

	@XmlElement(name = "created_at")
	@XmlJavaTypeAdapter(DateAdapter.class)
	public Date getCreatedAt() {
		return createdAt;
	}
	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	@XmlElement(name = "from_user")
	public String getFromUser() {
		return fromUser;
	}
	public void setFromUser(String fromUser) {
		this.fromUser = fromUser;
	}

	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
}

DateAdapter.java

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {
	private SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");

	@Override
	public String marshal(Date date) {
		return dateFormat.format(date);
	}

	@Override
	public Date unmarshal(String string) throws ParseException {
		return dateFormat.parse(string);
	}
}

Demo

Now that we have our model, we are ready to create and run some sample code.

The interesting part (regarding StAXON) is the configuration of the JsonXMLInputFactory and JsonXMLOutputFactory instances:

  • The PROP_VIRTUAL_ROOT input/output properties are required to pretend the existence of the searchResult root element. The element is inserted when reading and removed when writing.
  • The PROP_AUTO_ARRAY output property auto-detects multiple elements (result) and wraps them into JSON arrays. (Unfortunately there's no way in JAXB to add processing instructions, so we can't make use of StAXON's <?xml-multiple?> mechanism.)
  • The PROP_PRETTY_PRINT output property adds some formatting for better readability.

Everything else is straight JAXB!

Demo.java

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import de.odysseus.staxon.json.JsonXMLInputFactory;
import de.odysseus.staxon.json.JsonXMLOutputFactory;

public class Demo {
	/**
	 * Unmarshal/marshal JSON returned from Twitter search using JAXB.
	 * @param args ignore
	 * @throws IOException
	 * @throws XMLStreamException
	 * @throws JAXBException
	 */
	public static void main(String[] args) throws IOException, XMLStreamException, JAXBException {
		/*
		 * Search for tweets containing the #JAXB" hashtag.
		 */
		InputStream input = new URL("http://search.twitter.com/search.json?q=%23JAXB").openStream();

		/*
		 * Obtain a StAX reader from StAXON. The JSON object returned from
		 * twitter does not include the "searchResults" root, so we add it as a
		 * "virtual" element. When reading, the virtual root will be added to
		 * the stream.
		 */
		XMLInputFactory inputFactory = new JsonXMLInputFactory();
		inputFactory.setProperty(JsonXMLInputFactory.PROP_VIRTUAL_ROOT, "searchResults");
		XMLStreamReader xmlStreamReader = inputFactory.createXMLStreamReader(input);

		/*
		 * Unmarshal using JAXB.
		 */
		JAXBContext jaxbContext = JAXBContext.newInstance(SearchResults.class);
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(xmlStreamReader);

		/*
		 * As per StAX specification, XMLStreamReader.close() doesn't close the
		 * underlying stream.
		 */
		input.close();

		/*
		 * Add an important item...
		 */
		Result result = new Result();
		result.setCreatedAt(new Date());
		result.setFromUser("me");
		result.setText("You can do #JAXB with StAXON too:)");
		searchResults.getResults().add(0, result);

		/*
		 * Obtain a StAX writer from StAXON. If we want to insert proper JSON
		 * array boundaries, we need to set the <code>PROP_AUTO_ARRAY</code>
		 * property. In analogy to the input factory configuration, we specify
		 * our "virtual" root to remove it from the stream when writing.
		 */
		XMLOutputFactory outputFactory = new JsonXMLOutputFactory();
		outputFactory.setProperty(JsonXMLOutputFactory.PROP_VIRTUAL_ROOT, "searchResults");
		outputFactory.setProperty(JsonXMLOutputFactory.PROP_AUTO_ARRAY, true);
		outputFactory.setProperty(JsonXMLOutputFactory.PROP_PRETTY_PRINT, true);
		XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(System.out);

		/*
		 * Marshal using JAXB.
		 */
		Marshaller marshaller = jaxbContext.createMarshaller();
		marshaller.marshal(searchResults, xmlStreamWriter);
	}
}

Running the sample will produce something like this:

{
  "completed_in" : "0.071",
  "query" : "%23JAXB",
  "results" : [ {
    "created_at" : "Sat, 17 Sep 2011 21:45:33 +0200",
    "from_user" : "me",
    "text" : "You can do #JAXB with StAXON too:)"
  },

  remvoved some items...

  {
    "created_at" : "Fri, 12 Aug 2011 15:06:43 -0400",
    "from_user" : "bdoughan",
    "text" : "You can now use EclipseLink JAXB (MOXy) with JSON :)"
  } ]
}