Writing JSON - beckchr/staxon GitHub Wiki

StAXON supports writing JSON using the javax.xml.stream.XMLStreamWriter API.

Creating a JSON XMLStreamWriter

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.

Writing Documents

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, the writer.close() statement as well as the <?xml version="1.0" ?> XML directive.

Writing Text

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.

Writing Nested Elements

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.

Writing Attributes

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 @.

Writing "The Dollar"

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.

Writing Namespace Declarations

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
  }
}

Writing Arrays

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.

Writing Primitives

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
}
⚠️ **GitHub.com Fallback** ⚠️