Custom jackson date deserializer

Learn how to deserialize multiple date formats when unmarshalling json using jackson.

Detailed Video Notes

Jackson handles serialization and deserialization of dates by defaulting to use GMT, greenwich mean time (Time Zone Abbreviation), for all date processing unless otherwise configured. In the event you expose a rest service that multiple clients post to, you may want the flexibility to support multiple date formats. Lets find out how to handle various date formats by writing a custom date deserializer.

Project setup

[0:51]

We created a maven project by using a simple archetype quick start and imported dependencies jackson and apache commons lang.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.5.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.5.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

Creating a Deserializer

[0:16]

Just to recap, serialization and deserialization is the process of converting to/from json. For instance, you may have json array that you want to convert to an arraylist using jackson. Or you could perform the same conversion with gson. When a user would post to a service or if we consume json by reading in a file we would take the json and convert it to a java object. When a date is read, we would want it to be parsed as a date not as a string.

To do this we will create a custom jackson deserializer by extending UntypedObjectDeserializer, a deserializer implementation used if it is necessary to bind content of an unknown type. We will first check if the token is a string and if it is use apache commons parse date passing in multiple formats. If an exception is thrown we will just parse using the context.

class CustomDateDeseralizer extends UntypedObjectDeserializer {

    private static final long serialVersionUID = -2275951539867772400L;

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException {

        if (jp.getCurrentTokenId() == JsonTokenId.ID_STRING) {
            try {
                return DateUtils.parseDate(jp.getText(), new String[] {
                        "MM/dd/yyyy", "yyyy.MM.dd G 'at' HH:mm:ss z" });
            } catch (Exception e) {
                return super.deserialize(jp, ctxt);
            }
        } else {
            return super.deserialize(jp, ctxt);
        }
    }
}

Registering Deserializer as a SimpleModule

[1:13]

Once the deserializer is registered we must instruct ObjectMapper how we want to proceed if it hits a date. In Jackson 1.7, modules were introduced to extend jackson functionality so we can create a SimpleModule and register our CustomDateDeseralizer class.

private ObjectMapper configureObjectMapper() {

    ObjectMapper objectMapper = new ObjectMapper();

    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addDeserializer(Object.class, new CustomDateDeseralizer());
    objectMapper.registerModule(simpleModule);

    return objectMapper;
}

Registering ObjectMapper with spring boot

If you are using spring boot, it configures an ObjectMapper automatically for you. To override default configuration simply create a bean called objectMapper and supply your configuration. It might look something like this:

@Bean
public ObjectMapper objectMapper() {

    ObjectMapper objectMapper = new ObjectMapper();

    // register your module
    
    return objectMapper;
}

Putting it together

[1:29]

Creating a sample json with a string and two dates we will want to run this through the ObjectMapper and verify that it creates java.util.date objects. When calling objectMapper.readValue we will specify that the json keys and values will be collected in a HashMap. Next looping through the entries we will output the key, value and the value class type.

Sample JSON

{
    "name": "Justin Musgrove", 
    "shortDateFormat": "03/05/2015",
    "longDateFormat": "2001.07.04 AD at 12:08:56 PDT"
}

Program

String objectAsJson = "{\"name\": \"Justin Musgrove\", \"shortDateFormat\": \"03/05/2015\",\"longDateFormat\": \"2001.07.04 AD at 12:08:56 PDT\"}";

@Test
public void deserializeDates() throws JsonParseException,
        JsonMappingException, IOException {

    ObjectMapper objectMapper = configureObjectMapper();

    @SuppressWarnings("unchecked")
    Map<Object, Object> jsonAsMap = objectMapper.readValue(objectAsJson,
            HashMap.class);

    for (Entry<Object, Object> entry : jsonAsMap.entrySet()) {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue());
        System.out.println(entry.getValue().getClass());
        System.out.println("----");
    }
}

Output

name
Justin Musgrove
class java.lang.String
----
shortDateFormat
Thu Mar 05 00:00:00 CST 2015
class java.util.Date
----
longDateFormat
Wed Jul 04 14:08:56 CDT 2001
class java.util.Date
----

Thanks for joining in today's level up, have a great day!