Round-Trip Conversions: XML ➜ JSON with JsonWizard vs JsonFormatter

In the previous post we compared JsonWizard and JsonFormatter when converting JSON to XML. This time we’ll walk the road in reverse, converting XML back to JSON and analysing how each service copes with nested arrays, duplicate element names and the mandatory XML root node. As a senior Java engineer, I’ll keep the examples Java-centric so you can reproduce the findings in your own integration tests.

1. The baseline JSON

{
  "name": "Belgium",
  "capital": "Brussels",
  "population": 11589623,
  "area": 30528,
  "currency": "Euro",
  "languages": [
    "Flemish",
    "French",
    "German"
  ]
}

We purposely added a languages array because arrays are where many XML ↔ JSON converters fail.

2. JsonFormatter’s XML output (problematic)

<?xml version="1.0" encoding="UTF-8"?>
<name>Belgium</name>
<capital>Brussels</capital>
<population>11589623</population>
<area>30528</area>
<currency>Euro</currency>
<languages>Flemish</languages>
<languages>French</languages>
<languages>German</languages>

Notice the absence of a single root element and the three duplicate <languages> tags. Both issues violate XML best-practice and will confuse any round-trip converter.

Attempt #1 – XML ➜ JSON with JsonFormatter

[null]

JsonFormatter silently returns null. No exception, no console log, just an empty result. The duplicate element names and missing root apparently cause an internal failure that never surfaces to the user—leaving you guessing what went wrong.

3. JsonWizard’s XML output (well-formed)

<root>
  <name>Belgium</name>
  <capital>Brussels</capital>
  <population>11589623</population>
  <area>30528</area>
  <currency>Euro</currency>
  <languages>
    <language>Flemish</language>
    <language>French</language>
    <language>German</language>
  </languages>
</root>

We now have a single <root> wrapper and a properly nested <languages> section, paving the way for a safe XML ➡ JSON round trip.

Attempt #2 – XML ➜ JSON with JsonWizard

{
  "root": {
    "name": "Belgium",
    "capital": "Brussels",
    "population": 11589623,
    "area": 30528,
    "currency": "Euro",
    "languages": {
      "language": [
        "Flemish",
        "French",
        "German"
      ]
    }
  }
}

The result is not byte-for-byte identical to our original JSON, but it is semantically equivalent: all values are preserved and the languages list is intact. The extra root node appears because XML requires exactly one top-level element. Converters therefore wrap everything in a dummy element and keep it when returning to JSON to avoid information loss.

Why converters keep the <root> wrapper

XML’s well-formedness rule says a document must have a single root. JSON, on the other hand, allows multiple properties at the top level. If a converter simply discarded the wrapper on the way back, it would not know how to name the top-level object when converting the JSON again or generating an XSD. Retaining root is therefore the safest reversible choice—though some libraries let you strip it manually if you don’t need round-trip symmetry.

4. Verifying the round trip in Java

Below is a minimal JUnit 5 test that should pass when using JsonWizard’s output and fail with JsonFormatter’s. The snippet relies on com.fasterxml.jackson.dataformat:jackson-dataformat-xml v2.19.0. Note: Let's copy the above XML code into a file and save it as country.xml for this test.

@Test
void shouldRoundTripCountry() throws Exception {
  XmlMapper xml = new XmlMapper();
  ObjectMapper json = new ObjectMapper();

  String xmlText = Files.readString(Path.of("country.xml"));
  Country country = xml.readValue(xmlText, Country.class);

  String jsonOut = json.writerWithDefaultPrettyPrinter().writeValueAsString(country);

  Country copy = json.readValue(jsonOut, Country.class);
  assertEquals(country, copy);
}

@Data
static class Country {
  public String name;
  public String capital;
  public String population;
  public String area;
  public String currency;

  @JacksonXmlElementWrapper(localName = "languages")
  @JacksonXmlProperty(localName = "language")
  public List<String> languages;
}

5. Takeaways

JsonWizard succeeds because it keeps XML well-formed (single root, unique child names) and exposes useful error messages. JsonFormatter stumbles on the same input, returning null without context—a risky black box for automated workflows.

Tested on June 1 2025 with the public versions of JSONFormatter and JsonWizard. Results may change as these services evolve.