Converting Between XML and JSON Using JAXB and Jackson

JAXB converts XML to Java and Jackson handles the Java to JSON Conversion

“You only live once, but if you do it right, once is enough.” ― Mae West

1. Introduction

In a previous article we covered the basics of using JAXB with its annotations to convert java objects to XML and back. In this article, we take a look at converting from XML to JSON and back, using jackson for the JSON conversion.

We use the same example XML for illustrating the difference in conversion of XML versus JSON. Here is a snippet of the XML:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications 
      with XML.</description>
   </book>
   <book id="bk102">
      <author>Ralls, Kim</author>
      <title>Midnight Rain</title>
...

2. Java Object Model

We use the following classes for converting to and from XML.

2.1. The Catalog class

The Catalog class is the same as earlier. It works both for converting XML using JAXB as well as converting JSON using Jackson. No Jackson specific annotations required here.

package sample;

import java.util.List;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;

@XmlAccessorType(XmlAccessType.FIELD)
public class Catalog
{
    @XmlElement(name = "book")
    private List<Book> books = new ArrayList<Book>();

    public List<Book> getBooks()
    {
        return books;
    }

    public void setBooks(List<Book> books)
    {
        this.books = books;
    }
}

2.2. The Book class

The Book class requires a few changes. First off let us attempt to use it as is and see if it works. Here is the Book class defined as before:

package sample;

import java.math.BigDecimal;
import java.time.LocalDate;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
public class Book
{
    @XmlAttribute
    private String id;
    private String author;
    private String title;
    private String genre;
    private BigDecimal price;

    @XmlElement(name = "publish_date")
    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    private LocalDate pubdate;
    private String description;

    public String getId()
    {
        return id;
    }

    public void setId(String id)
    {
        this.id = id;
    }

    public String getAuthor()
    {
        return author;
    }

    public void setAuthor(String author)
    {
        this.author = author;
    }

    public String getTitle()
    {
        return title;
    }

    public void setTitle(String title)
    {
        this.title = title;
    }

    public String getGenre()
    {
        return genre;
    }

    public void setGenre(String genre)
    {
        this.genre = genre;
    }

    public BigDecimal getPrice()
    {
        return price;
    }

    public void setPrice(BigDecimal price)
    {
        this.price = price;
    }

    public LocalDate getPubdate() {
        return pubdate;
    }

    public void setPubdate(LocalDate pubdate) {
        this.pubdate = pubdate;
    }

    public void setPubdate(String pubdate)
    {
        this.pubdate = LocalDate.parse(pubdate);
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(String description)
    {
        this.description = description;
    }
}

3. Converting XML to JSON

And here is how we convert XML to JSON. First load the XML using JAXB.

Catalog catalog = JAXB.unmarshal(new File(xmlFile), Catalog.class);

Next is to serialize the java object to JSON.

ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(System.out, catalog);

The conversion proceeds without exceptions and we get the following JSON output.

{
    "books": [
        {
            "author": "Gambardella, Matthew",
            "description": "An in-depth look at creating applications \n      with XML.",
            "genre": "Computer",
            "id": "bk101",
            "price": 44.95,
            "pubdate": {
                "chronology": {
                    "calendarType": "iso8601",
                    "id": "ISO"
                },
                "dayOfMonth": 1,
                "dayOfWeek": "SUNDAY",
                "dayOfYear": 275,
                "era": "CE",
                "leapYear": true,
                "month": "OCTOBER",
                "monthValue": 10,
                "year": 2000
            },
            "title": "XML Developer's Guide"
        },
        {
            "author": "Ralls, Kim",
            "description": "A former architect battles corporate zombies, \n      an evil sorceress, and her own childhood to become queen \n      of the world.",
            "genre": "Fantasy",
            "id": "bk102",
            "price": 5.95,
...

Immediately, we notice that the file publish_date in XML is serialized as pubdate in JSON since that is the field name in the java class. Also, full details of the field values for pubdate are serialized, whereas we would like just the date in the format 2000-10-01.

4. Converting JSON to XML

Let us attempt to convert this JSON back into XML and see if it works. The code for doing so basically reverses what we did above: use Jackson to deserialize JSON into our java objects, and serialize that data into XML using JAXB.

ObjectMapper mapper = new ObjectMapper();
Catalog catalog = mapper.readValue(new File(jsonFile), Catalog.class);
JAXB.marshal(catalog, System.out);

On running this code, however, we get the following error:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDate` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (File); line: 1, column: 133] (through reference chain: sample.Catalog["books"]->java.util.ArrayList[0]->sample.Book["pubdate"])
        at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
        at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1438)
...

On deciphering the somewhat complex message shown above, we come to the conclusion that Jackson failed trying to convert the pubdate JSON values into a LocalDate instance – there are no constructors available for doing so.

So what do we do?

5. Changing the JSON Field Name

First let us change the JSON field name from pubdate to publish_date. This is accomplished very easily using the Jackson annotations in the Book class as shown below.

...
@XmlAccessorType(XmlAccessType.FIELD)
public class Book
{
    ...
    @JsonProperty("publish_date")
    public LocalDate getPubdate() {
        return pubdate;
    }

    @JsonProperty("publish_date")
    public void setPubdate(LocalDate pubdate) {
        this.pubdate = pubdate;
    }
    ...
}

These annotations will take care of the field name both for serialization as well as deserialization.

6. Using Adapter Classes to Control JSON Format

Since the LocalDate class does not have a constructor accepting a String, we need to use adapter classes for (de-)serialization.

Here is the class to be used for serialization. It uses LocalDate.toString() to generate a string representation of the date in the format 2000-10-01, rather than the long format with all the fields shown previously.

public class LocalDateSerializer extends JsonSerializer<LocalDate>
{
    public void serialize(LocalDate arg0,
                          JsonGenerator arg1,
                          SerializerProvider arg2)
        throws java.io.IOException, JsonProcessingException {
        arg1.writeString(arg0.toString());
    }
}

And the corresponding deserializer is shown below. It uses LocalDate.parse() to convert from a date string in the format 2000-10-01 to a LocalDate instance.

public class LocalDateDeserializer extends JsonDeserializer<LocalDate>
{
    public LocalDate deserialize(JsonParser arg0,DeserializationContext arg1)
        throws java.io.IOException, JsonProcessingException {
        return LocalDate.parse(arg0.getText());
    }
}

Once these classes are defined, we need to tell Jackson to use them. One way to do it is to use annotations in the Book class.

...
@XmlAccessorType(XmlAccessType.FIELD)
public class Book
{
    ...
    @XmlElement(name = "publish_date")
    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    @JsonSerialize(using = JacksonLocalDateAdapter.Serializer.class)
    @JsonDeserialize(using = JacksonLocalDateAdapter.Deserializer.class)
    private LocalDate pubdate;
    ...
}

Once these changes are implemented, conversion to JSON proceeds smoothly with the following changes: 1. Using publish_date instead of pubdate as before 2. The field publish_date formatted as a string.

{
    "books": [
        {
            "author": "Gambardella, Matthew",
            "description": "An in-depth look at creating applications \n      with XML.",
            "genre": "Computer",
            "id": "bk101",
            "price": 44.95,
            "publish_date": "2000-10-01",
            "title": "XML Developer's Guide"
        },
        {
            "author": "Ralls, Kim",
            "description": "A former architect battles corporate zombies, \n      an evil sorceress, and her own childhood to become queen \n      of the world.",
            "genre": "Fantasy",
...

The conversion from JSON to XML is similarly correct.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<catalog>
    <book id="bk101">
        <author>Gambardella, Matthew</author>
        <title>XML Developer's Guide</title>
        <genre>Computer</genre>
        <price>44.95</price>
        <publish_date>2000-10-01</publish_date>
        <description>An in-depth look at creating applications
      with XML.</description>
    </book>
    <book id="bk102">
        <author>Ralls, Kim</author>
        <title>Midnight Rain</title>
        <genre>Fantasy</genre>
...

And that is one way you can convert XML to JSON and back!

Conclusion

When you need to convert XML to JSON and back, you can use JAXB with Jackson for the conversion. JAXB converts XML to java objects, and Jackson converts the same java objects to JSON. We did need a few adjustements to get the format we desired, though.