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.
To make JAXB-bindings to JSON as easy as possible, StAXON provides the ability to configure the marshal
and unmarshal process with the @JsonXML
annotation.
It has the following properties:
-
autoArray
(boolean) - whether to automatically add JSON array boundaries (default isfalse
) -
virtualRoot
(boolean) - whether the JSON strips the root element (default isfalse
) -
prettyPrint
(boolean) - whether to format output for better readability (default isfalse
) -
namespaceSeparator
(char) - character used as namespace prefix separator (default is':'
) -
namespaceDeclarations
(boolean) - whether to write namespace declarations (default istrue
) -
multiplePaths
(String[]) - absolute or relative (suffix) paths to array elements (default is none)
A JsonXMLMapper<T>
instance can then be used to read/write JSON objects or arrays of a JAXB-annotated
model type (@XmlRootElement
or @XmlType
) T
via the following methods:
T readObject(Reader reader) throws JAXBException, XMLStreamException
void writeObject(Writer writer, T value) throws JAXBException, XMLStreamException
List<T> readArray(Reader reader) throws JAXBException, XMLStreamException
void writeArray(Writer writer, Collection<T> collection) throws JAXBException, XMLStreamException
The mapper will detect a JsonXML
annotation on type T
if present.
This sample has been inspired by JSON Binding with EclipseLink MOXy - Twitter Example by Blaise Doughan.
Our sample request we'll send over to Twitter is
http://search.twitter.com/search.json?q=%23JAXB
to search for tweets containing the #JAXB
hashtag. Then, we'll use StAXON and JAXB to
- unmarshal the JSON response
- modify the resulting Java model
- marshal the model back to JSON
First of all, we need a JAXB-annotated model.
For the sake of simplicity, our model classes only contain a subset of the properties
provided by the Twitter Search API.
The root element class will be SearchResults
, which contains a list of Result
objects.
Finally, 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;
import de.odysseus.staxon.json.jaxb.JsonXML;
@JsonXML(
virtualRoot = true, // JSON will omit "customer" root
multiplePaths = "/results", // trigger JSON array for "phone" list
prettyPrint = true) // produce formatted JSON output
@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.
TwitterDemo.java
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
import de.odysseus.staxon.json.jaxb.JsonXMLMapper;
/**
* Twitter JAXB demo.
*/
public class TwitterDemo {
/**
* Read/write JSON returned from Twitter search using JAXB.
* @param args ignored
* @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();
/*
* Create mapper instance.
*/
JsonXMLMapper<SearchResults> mapper = new JsonXMLMapper<SearchResults>(SearchResults.class);
/*
* Read tweets.
*/
SearchResults searchResults = mapper.readObject(input);
input.close();
/*
* Add an item for fun...
*/
Result result = new Result();
result.setCreatedAt(new Date());
result.setFromUser("ChristophBeck");
result.setText("You can do #JAXB with StAXON too:)");
searchResults.getResults().add(0, result);
/*
* Write back to console.
*/
mapper.writeObject(System.out, searchResults);
}
}
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 :)"
} ]
}
Consider that - for some reason - we do not want to (or cannot) use the @JsonXML
and JsonXMLMapper
mechanism.
In this case, we have to configure our StAX readers and writers manually and interact with JAXB directly.
In order to trigger a JSON array for our SearchResults.results
list, we are decorating our writer with an
XMLMultipleStreamWriter
and pass it the element path to our list as an array path:
xmlStreamWriter = new XMLMultipleStreamWriter(xmlStreamWriter, "/results");
The wrapper triggers a JSON array start by inserting an <?xml-multiple?>
processing instruction into the stream before
writing a sequence of elements matching one of its array paths. See Mastering Arrays for more on dealing with JSON arrays.
Everything else is straight JAXB!
TwitterDemo.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import de.odysseus.staxon.json.JsonXMLConfig;
import de.odysseus.staxon.json.JsonXMLConfigBuilder;
import de.odysseus.staxon.json.JsonXMLInputFactory;
import de.odysseus.staxon.json.JsonXMLOutputFactory;
import de.odysseus.staxon.json.util.XMLMultipleStreamWriter;
/**
* Twitter JAXB demo.
*/
public class TwitterDemo {
/**
* Unmarshal/marshal JSON returned from Twitter search using JAXB.
* @param args ignored
* @throws IOException
* @throws XMLStreamException
* @throws JAXBException
*/
public static void main(String[] args) throws IOException, XMLStreamException, JAXBException {
/*
* Search for tweets containing the #JAXB" hashtag.
*/
URL url = new URL("http://search.twitter.com/search.json?q=%23JAXB");
/*
* The JSON object returned from twitter does not include the "searchResults" root,
* so we specify it as "virtual" root element. When reading, the virtual root will be
* added to the stream. When writing, the "virtual" root will be removed from the stream.
*/
JsonXMLConfig config = new JsonXMLConfigBuilder().
virtualRoot("searchResults").
prettyPrint(true).
build();
/*
* Obtain a StAX reader from StAXON.
*/
InputStream input = url.openStream();
XMLStreamReader reader = new JsonXMLInputFactory(config).createXMLStreamReader(input);
/*
* Create JAXB context.
*/
JAXBContext context = JAXBContext.newInstance(SearchResults.class);
/*
* Unmarshal using JAXB.
*/
SearchResults searchResults = (SearchResults) context.createUnmarshaller().unmarshal(reader);
/*
* As per StAX specification, XMLStreamReader.close() doesn't close the
* underlying stream.
*/
input.close();
/*
* Add an item for fun...
*/
Result result = new Result();
result.setCreatedAt(new Date());
result.setFromUser("ChristophBeck");
result.setText("You can do #JAXB with StAXON too:)");
searchResults.getResults().add(0, result);
/*
* Obtain a StAX writer from StAXON.
*/
OutputStream output = System.out;
XMLStreamWriter writer = new JsonXMLOutputFactory(config).createXMLStreamWriter(output);
/*
* To insert JSON array boundaries, we wrap our writer into an XMLMultipleStreamWriter.
*/
writer = new XMLMultipleStreamWriter(writer, false, "/results");
/*
* Marshal using JAXB.
*/
context.createMarshaller().marshal(searchResults, writer);
}
}