Using Jackson `JsonNode` to write XML

(how to avoid unnecessary <ObjectNode> wrapper)

@cowtowncoder
3 min readJul 7, 2021

With Jackson 2.12, Jackson XML format module (jackson-dataformat-xml) functionality improved a lot (see this earlier blog post). Specifically it became possible to actually use Jackson’s Tree Model (JsonNode) for reading and writing XML.

Reading XML into JsonNode with Jackson 2.12

By default you can read XML content like:

<root>
<value>Hello</value>
</root>

with XmlMapper (using xmlMapper.readTree(xmlSource)) and get same Tree value as if reading this JSON with regular JsonMapper:

{"value":"Hello"}

But if you serialize resulting JsonNode (xmlMapper.writeValueAsString(treeValue)) output will look something like:

<ObjectNode><value>Hello</value></ObjectNode>

which is consistent structurally (there is the fundamental structural mismatch between XML and JSON, “structural impedance”) but not quite what you would typically want.

Disparity between XML and JSON data models

The problem is that whereas ObjectNode s (and JSON Objects) are “unnamed” (there is no name associated at root level), in XML elements are always named.
This particular case could be fixed by explicitly specifying “Root Name” (instead of Jackson using either annotation in the class, or lacking one, simple name of the class):

String xml = xmlMapper.writer().withRootName("root")
.writeValueAsString();

But more generally you might simply want to avoid the outermost element altogether.

“Unwrapping” XML written from JsonNode

Consider thecase of starting with example like:

{ "message": {
"id" : 3,
"content" : "Hello!"
}

(either read from JSON (and possibly modified) or explicitly constructed using ObjectMapper.createObjectNode() and adding properties).
We would probably want to get XML like:

<message>
<id>3</id>
<content>Hello!</content>
</message>

but by default logical content of JSON message, expressed as JsonNode, would become:

<ObjectNode>
<message>
<id>3</id>
<content>Hello!</content>
</message>
</ObjectNode>

Here we want to “unwrap” that <message> content. First part is easy — we can just take the value of the outermost ObjectNode — but that does not quite lead to what we want:

String xml = xmlMapper.writeValueAsString(rootNode.get("message"));// becomes:<ObjectNode>
<id>3</id>
<content>Hello!</content>
</ObjectNode>

but we can combine root name setting with automatic “unwrapping” for the specific case using code like so:

JsonNode rootNode = ...;
if (rootNode instanceof ObjectNode) {
ObjectNode ob = (ObjectNode) rootNode;
if (ob.size() == 1) { // can only unwrap if 1 entry (XML must have 1 root element)
Map.Entry<String, JsonNode> entry = ob.fields().next();
String unwrappedXml = xmlMapper.writer()
.withDefaultPrettyPrinter()
.withRootName(entry.getKey())
.writeValueAsString(entry.getValue());
}
}
// We get what we wanted!
<message>
<id>3</id>
<content>Hello!</content>
</message>

This is not a whole lot of code to write but still unfortunate boilerplate: for now, you’d probably want to write a helper method.

Jackson 2.13 will make it easier

Given that such functionality, summarized as:

  1. If value written is ObjectNode with one (and only one) entry
  2. and that entry has Object or scalar value (not ArrayNode since that could produce invalid XML)
  3. Unwrap result so that root name is the name of the entry, and contents the value

seems generally used, Jackson 2.13 will add this functionality by introduce a new feature: ToXmlGenerator.Feature.UNWRAP_ROOT_OBJECT_NODE — enabling of which will automatically do this.

And Jackformer supports it too

To try this out before Jackson 2.13.0 gets released, you may also want to check out Jackformer tool (which I recently wrote a bit about, see “Introducing Jackformer”): version 0.5.2 automatically uses this handling when output format of transformation is XML:

docker run --publish 8080:8080 -t cowtowncoder/jackformer-webapp:0.5.2

--

--

@cowtowncoder

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox