Writing JSON - beckchr/staxon GitHub Wiki
StAXON supports writing JSON using the javax.xml.stream.XMLStreamWriter
API.
The easiest way to create a writer instance is to use the various XMLOutputFactory.createXMLStreamWriter(...)
methods, e.g.
XMLOutputFactory factory = new JsonXMLOutputFactory();
factory.setProperty(JsonXMLOutputFactory.PROP_PRETTY_PRINT, true);
XMLStreamWriter writer = factory.createXMLStreamWriter(stream);
The stream
parameter can be a java.io.OutputStream
or java.io.Writer
.
The output factory will use an instance of JsonStreamFactory
to create underlying JSON stream targets (default is to use
StAXON's own implementation). You may also explicitly pass an instance to the stream factory, e.g.:
XMLOutputFactory factory = new JsonXMLOutputFactory(new JacksonStreamFactory(...));
Please refer to Factory Configuration for a description of available output properties.
If you know StAX, you'll notice that there's nothing special here: just use the XMLStreamWriter
obtained from StAXON as usual.
In fact, if you already have some code which writes XML and want to switch to JSON, just replacing the writer should do the trick.
OK, there's a tricky part with JSON arrays, which we'll care about later.
The more important part here is to understand how StAXON maps the various XML artifacts (elements, attributes namespaces, characters, etc) to JSON. Unfortunately, there is no "standard way" to do this, so what we're interested to learn about here is StAXON's Mapping Convention.
Let's start and code a document containing just a single empty element.
JsonXMLStreamWriter writer = factory.createXMLStreamWriter(System.out);
writer.writeStartDocument();
writer.writeEmptyElement("alice");
writer.writeEndDocument();
writer.close();
With an XML-based writer, this would have produced something like
<?xml version="1.0" ?>
<alice/>
However, with our JSON-based writer, the output is
{
"alice" : null
}
That is, an element without any attributes and content is mapped to a JSON property with value null.
In the following examples, we'll omit the definition of the
writer
variable, thewriter.close()
statement as well as the<?xml version="1.0" ?>
XML directive.
Now we're adding some text to our root element.
Java
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeCharacters("charlie");
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice>charlie</alice>
JSON
{
"alice" : "charlie"
}
The text content appears as the value of the property corresponding to the XML element.
Now what if we add a sub-element?
Java
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeEmptyElement("bob");
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice>
<bob/>
</alice>
JSON
{
"alice" : {
"bob" : null
}
}
So, a child element is mapped to a property of the JSON object corresponding to the parent element.
We are adding an attribute to the root element:
Java
writer.writeStartDocument();
writer.writeEmptyElement("alice");
writer.writeAttribute("david", "edgar");
writer.writeEndDocument();
XML
<alice david="edgar"/>
JSON
{
"alice" : {
"@david" : "edgar"
}
}
As you can see, an XML attribute is mapped to a JSON property whose name is prefixed with @.
So far we have seen examples where an element has either attributes or text, not both. If we look at our previous text
mapping example, we may expect to run into a problem when we have to create a JSON object (with {...}
) for an element
which also has text. Here's what's happening:
Java
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeAttribute("david", "edgar");
writer.writeCharacters("charlie");
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice david="edgar">charlie</alice>
JSON
{
"alice" : {
"@david" : "edgar",
"$" : "charlie"
}
}
So, within JSON objects, text is mapped to the special '$' property.
Let's start with a simple default namespace declaration:
Java
writer.setDefaultNamespace("http://edgar/david");
writer.writeStartDocument();
writer.writeEmptyElement("alice");
writer.writeEndDocument();
XML
<alice xmlns="http://edgar/david"/>
JSON
{
"alice" : {
"@xmlns" : "http://edgar/david"
}
}
OK, this is just like mapping an attribute. Because xmlns
cannot be used as an attribute name, there's no ambiguity here.
Next we'll take a look at namespace declarations with prefixes.
Java
writer.setPrefix("peter", "http://edgar/david");
writer.writeStartDocument();
writer.writeEmptyElement("http://edgar/david", "alice");
writer.writeNamespace("peter", "http://edgar/david");
writer.writeEndDocument();
XML
<peter:alice xmlns:peter="http://edgar/david"/>
JSON
{
"peter:alice" : {
"@xmlns:peter" : "http://edgar/david",
}
}
Of course it's also possible to have more than one namespace in a document. To illustrate this slightly more complex scenario, consider
Java
writer.setDefaultNamespace("http://edgar");
writer.setPrefix("peter", "http://david");
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeDefaultNamespace("http://edgar");
writer.writeNamespace("peter", "http://david");
writer.writeEmptyElement("http://david", "bob");
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice xmlns="http://edgar" xmlns:peter="http://david">
<peter:bob/>
</alice>
JSON
{
"alice" : {
"@xmlns" : "http://edgar",
"@xmlns:peter" : "http://david",
"peter:bob" : null
}
}
There is no such thing like an array in XML.
StAXON lets you map a sequence of XML elements with the same name to a JSON array using a processing instruction.
The instruction target is "xml-multiple"
and may optionally take the array field name as data.
There's no end array hint as StAXON detects the end of an array sequence and closes it automatically.
Java
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeProcessingInstruction(JsonXMLStreamConstants.MULTIPLE_PI_TARGET, "bob");
writer.writeStartElement("bob");
writer.writeCharacters("edgar");
writer.writeEndElement();
writer.writeStartElement("bob");
writer.writeCharacters("charlie");
writer.writeEndElement();
writer.writeEmptyElement("peter");
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice>
<?xml-multiple bob?>
<bob>edgar</bob>
<bob>charlie</bob>
<peter/>
</alice>
JSON
{
"alice" : {
"bob" : [ "edgar", "charlie" ],
"peter" : null
}
}
Please refer to Mastering Arrays for more on this topic.
As an extension to the XMLStreamWriter
API, the JsonXMLStreamWriter
provides methods to write
JSON primitive (numbers and booleans):
Java
writer.writeStartDocument();
writer.writeStartElement("alice");
writer.writeNumber(123);
writer.writeEndElement();
writer.writeEndDocument();
XML
<alice>123</alice>
JSON
{
"alice" : 123
}