How to convert XML to JSON using only Jackson?

I am getting a response from server as XML. But I need to display this in JSON format.

Is there any way to convert it without any third party API? I used Jackson but for this I need to create POJO.

The response from server is like this:

<?xml version='1.0'?>
<errors><error><status>400</status><message>The field 'quantity' is invalid.</message><details><invalid_reason>The quantity specified is greater than the quantity of the product that is available to ship.</invalid_reason><available_quantity>0</available_quantity><order_product_id>12525</order_product_id></details></error></errors>

Solution 1:

Using Jackson 2.x

You can do that with Jackson and no POJOs are required for that:

String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
             "<errors>\n" +
             "  <error>\n" +
             "    <status>400</status>\n" +
             "    <message>The field 'quantity' is invalid.</message>\n" +
             "    <details>\n" +
             "      <invalid_reason>The quantity specified is greater than the quantity of the product that is available to ship.</invalid_reason>\n" +
             "      <available_quantity>0</available_quantity>\n" +
             "      <order_product_id>12525</order_product_id>\n" +
             "    </details>\n" +
             "  </error>\n" +
             "</errors>";

XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(xml.getBytes());

ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(node);

The following dependencies are required:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.8.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.8.2</version>
</dependency>

Be aware of the XmlMapper limitations stated in the documentation:

Tree Model is only supported in limited fashion: specifically, Java arrays and Collections can be written, but can not be read, since it is not possible to distinguish Arrays and Objects without additional information.

As nicely highlighted by the Jackson author in the comments, Jackson 2.12 finally improved XML handling, so that duplicates are preserved if using JsonNode or Object as target types.

Using JSON.org

You also can do it with JSON.org:

String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
             "<errors>\n" +
             "  <error>\n" +
             "    <status>400</status>\n" +
             "    <message>The field 'quantity' is invalid.</message>\n" +
             "    <details>\n" +
             "      <invalid_reason>The quantity specified is greater than the quantity of the product that is available to ship.</invalid_reason>\n" +
             "      <available_quantity>0</available_quantity>\n" +
             "      <order_product_id>12525</order_product_id>\n" +
             "    </details>\n" +
             "  </error>\n" +
             "</errors>";

String json = XML.toJSONObject(xml).toString();

The following dependency is required:

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20160810</version>
</dependency>

Solution 2:

I know that I am too late for an answer here. But I am writing this for the new guys who stumbled upon this question and thinking to use @Cassio's answer.

The problem of using XmlMpper to de-serialize to a JsonNode is that, when there are multiple elements with the same name at the same level, then it will replace the previous one with the new one and end up with data loss. Usually, we've to add this to an array. To tackle this problem, we can override the _handleDuplicateField() method of the JsonNodeDeserializer class. Enough talking. Let's see the code

public class DuplicateToArrayJsonNodeDeserializer extends JsonNodeDeserializer {

    @Override
    protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, 
        JsonNodeFactory nodeFactory,String fieldName, ObjectNode objectNode,
        JsonNode oldValue, JsonNode newValue) throws JsonProcessingException {
        ArrayNode node;
        if(oldValue instanceof ArrayNode){
            node = (ArrayNode) oldValue;
            node.add(newValue);
        } else {
            node = nodeFactory.arrayNode();
            node.add(oldValue);
            node.add(newValue);
        }
        objectNode.set(fieldName, node);
    }
}

Since we've overridden the default deserializer, we also need to register this in the XmlMapper to make it work.

XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new SimpleModule().addDeserializer(
    JsonNode.class, 
    new DuplicateToArrayJsonNodeDeserializer()
));
JsonNode node = xmlMapper.readTree(payLoad);