Using JAXB
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.
This sample has been inspired by JSON Binding with EclipseLink MOXy - Twitter Example by Blaise Doughan.
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);
}
}
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 thesearchResult
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 :)"
} ]
}