Handling JSON Dates with Camel

JSON is popular data interchange format for modern web services, but service implementations rarely deal with JSON internally. Typically, we want to work with objects that are native to the implementation language, for example, POJOs, if we are working with Java. The conversion between JSON and POJO should be fairly straight-forward, right? JSON is, after-all, "object notation". But what about dates and how they are formatted as strings?

So here is an example of a JSON representation of, well, me:

{
  "name":"David Gordon",
  "birthDate":"1986-12-26"
}

Okay so that date format is really familiar, yyyy-mm-dd.

In Java, it might be preferable to deal with the birth date attribute as a date object of some kind. Say for example, we want a service that calculates my legal drinking age, by adding 21 years to my birth date. That is tougher to do with a string than a Java date object.

A marshaling library is what we need to convert between the serialized representation and a POJO. There are a few choices out there, but for this article, I'll focus on Gson and Jackson.

When we're converting from JSON to a POJO, both libraries are smart enough to handle the date format in the example representation. That is mainly because for this example, a fairly common format was chosen. Some kind of really unique date format might not be handled so gracefully. In Camel Java DSL, we could consume this representation and convert to a POJO (even a POJO that hasn't even been defined) like this:

from("direct:in")         // JSON in
    .unmarshal().json()
    .to("direct:out");    // POJO out

However, even with such a common date format, if we try to convert the POJO back into JSON, things start to get weird. For example:

from("direct:in")         // JSON in
    .unmarshal().json()
    .marshal().json()
    .to("direct:out");    // JSON out

It should be noted, that the default marshaling library for Camel is Jackson. The resulting JSON looks like this:

{
  "name":"David Gordon",
  "birthDate":"Dec 26, 1986 12:00:00 AM"
}

So why did that happen? Jackson has a default date/time format. Since we haven't told it anything specific about what date format we want, it just goes with its default.

The Gson library will do something similar:

from("direct:in")         // JSON in
    .unmarshal().json(JsonLibrary.Gson)
    .marshal().json(JsonLibrary.Gson)
    .to("direct:out");    // JSON out

Resulting JSON representation:

{
  "name":"David Gordon",
  "birthDate":535939200000
}

So the question becomes, how to specify the desired date format? You can point both libraries at POJOs that are annotated with type adapters. For Jackson, a type adapter annotation looks like @XmlJavaTypeAdapter(DateAdapter.class), where a similar annotation that works with Gson looks like @JsonAdapter(DateAdapter.class).

This strategy works with all different types of objects, not just dates.

Here's an example of an adapter that works with Jackson:

public class DateAdapter extends XmlAdapter<String, Date> {

    private static final String FORMAT = "yyyy-mm-dd";
    private SimpleDateFormat format = new SimpleDateFormat();

    @Override
    public Date unmarshal(String date) throws Exception {

        format.applyPattern(FORMAT);

        try {
            return format.parse(date);
        } catch (ParseException e) {
            return null;
        }

    }

    @Override
    public String marshal(Date date) throws Exception {

        format.applyPattern(FORMAT);
        return format.format(date);

    }

}

And one that works with Gson:

public class DateAdapter extends TypeAdapter {

    private static final String FORMAT = "yyyy-mm-dd";
    private SimpleDateFormat format = new SimpleDateFormat();

    @Override
    public Date read(JsonReader jsonReader) throws IOException {

        format.applyPattern(FORMAT);
        String value = jsonReader.nextString();

        try {
            return format.parse(value);
        } catch (ParseException e) {
            return null;
        }

    }

    @Override
    public void write(JsonWriter jsonWriter, Object date) throws IOException {

        format.applyPattern(FORMAT);
        jsonWriter.value(format.format(date));

    }

}

For a working demonstration, have a look at a simple example Maven project: https://github.com/davgordo/camel-json-dates

comments powered by Disqus