Skip to content
This repository

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 is false)
  • autoPrimitive (boolean) - whether to automatically convert element text to JSON primitives (default is false)
  • virtualRoot (boolean) - whether the JSON strips the root element (default is false)
  • prettyPrint (boolean) - whether to format output for better readability (default is false)
  • namespaceSeparator (char) - character used as namespace prefix separator (default is ':')
  • namespaceDeclarations (boolean) - whether to write namespace declarations (default is true)
  • 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.

Twitter Example

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.

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 "searchResults" root
    multiplePaths = "/results", // trigger JSON array for "results" 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);
    }
}

Demo

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 :)"
  } ]
}

Demo, the hard way

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);
    }
}
Something went wrong with that request. Please try again.