Writing a Custom Jackson Serializer & Deserializer

Jackson is a mainstay of Java web development, providing JSON serialization & deserialization to Jersey, RestEasy, and Hadoop, among many more. Jackson can be easily integrated into most applications through the use of its data-binding annotations—simply mark fields to serialize with @JsonProperty and Jackson will happily create & read JSON representations of your object when you hand them to an ObjectMapper. Sometimes, though, you don’t have access to or don’t want to modify the source of an object that you want to use with Jackson. In these cases, you can write a custom module to provide Jackson with the information it needs to (de)JSONify your objects.

We recently ran across the no known serializer problem while trying to use Jackson (by way of Jersey) with an object that contained a Joda Time Duration. For future reference, the exception produced by Jersey looks like this:

com.sun.jersey.spi.container.ContainerResponse - The exception contained within MappableContainerException could not be mapped to a response, re-throwing to the HTTP container
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class org.joda.time.Duration]: can not instantiate from JSON object (need to add/enable type information?)
 at [Source: org.eclipse.jetty.server.HttpInput@1c7b0f4d; line: 1, column: 47]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:163) ~[jackson-databind-2.0.1.jar:na]

Since our application employed the Joda data type extensions module for Jackson, this error seemed curious, but looking into that project it became clear that translation is not provided for all of the Joda data types. Duration is an exceedingly simple object (it can be represented as a number of milliseconds), so writing our own serializer & deserializer is an easy endeavor.

public final class DurationSerializer extends StdScalarSerializer<Duration> {
    public DurationSerializer() { super(Duration.class); }

    @Override
    public void serialize(Duration value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
        JsonProcessingException {
        jgen.writeNumber(value.getMillis());
    }
}

Exceedingly simple, no? As mentioned before, a Duration is nothing more than an elapsed amount of time which we represent as milliseconds for ease of JSONing. Writing the deserializer to pull a Duration out of JSON is a similarly trivial bit of code:

public final class DurationDeserializer extends StdScalarDeserializer<Duration> {
    public DurationDeserializer() { super(Duration.class); }

    @Override
    public Duration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throws IOException, JsonProcessingException {
        if (jsonParser.getCurrentToken() == VALUE_NUMBER_INT) {
            return new Duration(jsonParser.getLongValue());
        }

        throw deserializationContext.mappingException("Expected JSON Number");
    }
}

Easy, we’re just verifying that the JSON token is an integer and then passing it to the Duration constructor. These two classes are the actual functionality of JSON reading and writing, but we need to tell Jackson about them. To do so, we’ll create a Jackson data-binding module:

public final class JodaDurationModule extends SimpleModule {
    public JodaDurationModule() {
        addDeserializer(Duration.class, new DurationDeserializer());
        addSerializer(Duration.class, new DurationSerializer());
    }
}

Now, tell Jackson about the module:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaDurationModule());

With that, Jackson now knows how to translate Joda Durations into JSON and back:

System.out.println(objectMapper.writer().writeValueAsString(new Duration(8)));

For completeness, here are the Maven dependency clauses for Jackson. The above code only requires jackson-core, but I mention the data-binding annotations and the Joda Time extensions:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.0.1</version>
</dependency>
<!-- For the aforementioned data-binding annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.0.1</version>
</dependency>
<!-- Extensions for Joda Time, which don't include Duration -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>2.0.1</version>
</dependency>

Posted by Drew Stephens @dinomite ·

Drew’s expertise lies in high-traffic web services, big data, and building hyper-efficient software teams. At Clearspring, Drew created and supported APIs that handled more than 6 million daily requests, monitored detailed metrics from processing clusters with hundreds of nodes, and delivered thousands of events per second to users in real-time. Drew is skilled in systems administration and takes automation seriously, applying the Unix adage "if you do it twice, you're doing it wrong" ruthlessly. As a certified Scrum Master, Drew worked alongside Ryan to build a kaizen-based development organization at Genius.com capable of delivering high-quality products as-promised and on-time. Drew spends his time away from computers lifting heavy things and racing cars that are well past their prime.

About Palomino Labs

Palomino Labs unlocks the potential of software to change people and industries. Our team of experienced software developers, designers, and product strategists can help turn any idea into reality.

See the Palomino Labs website for more information, or send us an email and let's start talking about how we can work together.