Using Jackson `JsonNode` to write XML

(how to avoid unnecessary <ObjectNode> wrapper)

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

<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

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

{ "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 = n.fields().next();
String unwrappedXml = xmlMapper.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

  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

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

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