What is in this post?

How to use AutoValue with Jackson to serialize and deserialize JSON.

Background

As part of my personal project, I found myself need to build a client library for a public web service. To get the custom client library going, I decided to start with the serialization/deserialization process which is a crucial part of the client library. While customized JSON handling is achievable, I find it less reliable than relying on the established library to do the trick. After some research on the Internet, I decided to choose AutoValue and Jackson to achieve the objective.

Libraries

AutoValue

GitHub Project Link

What is AutoValue

“AutoValue is a great tool for eliminating the drudgery of writing mundane value classes in Java. It encapsulates much of the advice in Effective Java Chapter 2, and frees you to concentrate on the more interesting aspects of your program. The resulting program is likely to be shorter, clearer, and freer of bugs. Two thumbs up.”

– Joshua Bloch, author, Effective Java

What does it have to do with the client library

In this client library I’m building, I use value class/object to represent the JSON response returned from the service for the following reasons:

1. Immutability

See Benefits of Immutable Objects in Java to learn more about why immutability is good. And in the client library context, once we received JSON from the server, it should never be changed.

2. Auto-Generate Value Class Boilerplate

Value class boilerplate methods such as ...Setters, toString(), .equals(), hashCode() are boring to implement, but they are critical for many reasons. AutoValue saves this problem and greately reduce the lines of code need to be manually maintained.

Jackson

GitHub Project Link

What is Jackson

Jackson has been known as “the Java JSON library” or “the best JSON parser for Java”. Or simply as “JSON for Java”.

– GitHub README

What does it have to do with the client library

The response returned from web service is in JSON format. Using a well-established library makes everything easier.

Ok, both libraries are great, how can we use them together?

In the below guide/examples, I’m going to demonstrate how to use AutoValue with Jackson library.

This guide assumes you have some knowledge how to use AutoValue. Please refer to Official User Guide

Sample Response & AutoValue

Sample AutoValue to respresent the response before applying any Jackson annotations.

Sample API Response

{
  "num_friends": 3,
  "num_blocked": 0,
  "num_invite_pending": 1,
  "friends": [
    {
      "name": "John Doe",
      "id": 1
    },
    {
      "name": "Jane Doe",
      "id": 2
    },
    {
      "name": "Justin Doe",
      "id": 3
    }
  ]
}

Sample AutoValue

// Friend.java - with static create method
@AutoValue
public abstract class Friend {
  public static Friend create(String name, int userId) {
    return new AutoValue_Friend(name, userId);
  }

  public abstract String name();
  public abstract int userId();
}

// FriendResponse.java - with builder
@AutoValue
public abstract class FriendResponse {
  public static Builder builder() {
    return new AutoValue_Friend.Builder();
  }

  public abstract int numFriends();
  public abstract int numBlocked();
  public abstract int numInvitePending();
  public abstract ImmutableList<Friend> friends();

  @AutoValue.Builder
  public abstract class Builder {
    public abstract Builder setNumFriends(int numFriends);
    public abstract Builder setNumBlocked(int numBlocked);
    public abstract Builder setNumInvitePending(int numInvitePending);
    public abstract Builder setFriends(ImmutableList<Friend> friends);
    public abstract FriendResponse build();
  }
}

Serialization

In the previous section, we have already defined all the necessary property for the JSON response in the AutoValue class definitions. How can we serialize the Java object into JSON string?

@JsonProperty annotation

Use @JsonProperty to let jackson library knows how a Java object attribute maps to a JSON attribute. The property name can be different if you want the java object attribute and the serialized attribute to have a different key name. Example: `userId (java)–> id (JSON)

@JsonSerialize annotation

Use @JsonSerialize to let jackson library knows how to serialize a object.

Annotated AutoValue Classes

@AutoValue
@JsonSerialize(as = Friend.class)
public abstract class Friend {
  // ignore create part

  @JsonProperty("name")
  public abstract String name();

  @JsonProperty("id")
  public abstract int userId();
}

// FriendResponse.java - with builder
@AutoValue
@JsonSerialize(as = FriendResponse.class)
public abstract class FriendResponse {

  @JsonProperty("num_friends")
  public abstract int numFriends();

  @JsonProperty("num_blocked")
  public abstract int numBlocked();

  @JsonProperty("num_invite_pending")
  public abstract int numInvitePending();

  @JsonProperty("friends")
  public abstract ImmutableList<Friend> friends();

  // ignore builder parts
}

Now, the Java object is ready to be serialized into JSON with jackson library using ObjectMapper.

final ObjectMapper objectMapper = JsonMapper.builder().build();
String jsonString = objectMapper.writeValueAsString(Friend.create("John Doe", 1));
// returns {"id":1,"name": "John"}

Deserialization

The deserialization counterpart is almost identical to the serialization one. However, depends on the creation mechanism you choose for the AutoValue create() vs buidler(), the annotations needed are different.

@JsonCreator annotation

Annotated @JsonProperty to the create() method parameters with the right JSON attribute to map use for the given parameter. Also, annotate the create() method itself with @JsonCreator (javadoc)

@AutoValue
public abstract class Friend {

  @JsonCreator
  public static Friend create(
    @JsonProperty("name") String name,
    @JsonProperty("id") int userId) {
    return new AutoValue_Friend(name, userId);
  }
  // ignore accessors part
}

@JsonDeserialize(builder = …) annotation

Annotate @JsonProperty to the setter methods inside the builder definition, and annotate the AutoValue class with @JsonDeserialize(builder = AutoValue_XXX.Builder.class (javadoc)

@AutoValue
@JsonDeserialize(builder = AutoValue_FriendResponse.Builder.class)
public abstract class FriendResponse {
  public static Builder builder() {
    return new AutoValue_Friend.Builder();
  }

  // ignore accessors parts

  @AutoValue.Builder
  public abstract class Builder {
    @JsonProperty("num_friends")
    public abstract Builder setNumFriends(int numFriends);

    @JsonProperty("num_blocked")
    public abstract Builder setNumBlocked(int numBlocked);

    @JsonProperty("num_invite_pending")
    public abstract Builder setNumInvitePending(int numInvitePending);

    @JsonProperty("friends")
    public abstract Builder setFriends(ImmutableList<Friend> friends);

    public abstract FriendResponse build();
  }
}

Now, the JSON response can be deserialized into Java AutoValue using object mapper.

import com.fasterxml.jackson.datatype.guava.GuavaModule;

// ...

ObjectMapper objectMapper =
    JsonMapper().builder()
        .addModule(new GuavaModule()) // ImmutableList dependency
        .build();
FriendResponse response = objectMapper.readValue(
  "{\"num_friends\":3,\"num_blocked\":0,\"num_invite_pending\":1,\"friends\":[{\"name\":\"John Doe\",\"id\":1},{\"name\":\"Jane Doe\",\"id\":2},{\"name\":\"Justin Doe\",\"id\":3}]}",
  FriendResponse.class);

Other Notes

Configure ObjectMapper

The jackson library uses ObjectMapper to handle the serialization and the deserialization. The behavior of the mapper can be configured to meet your needs. Below is an example mapper I use for all my convesions.

return JsonMapper.builder()
        // use convert string to Instant
        .addModule(new JavaTimeModule())
        // Immutable collection
        .addModule(new GuavaModule())
        // hide null value per google json guide
        // https://google.github.io/styleguide/jsoncstyleguide.xml?showone=Empty/Null_Property_Values#Empty/Null_Property_Values
        .serializationInclusion(Include.NON_NULL)
        // don't serialize Instant to integer/float timestamp. convert to string instead
        .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
        // don't fail on unknown property (not defined in @JsonProperty) for better maintainability
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        // sort for testing/debugging purpose]
        .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
        .build();

TL;DR

Serialization

  • Annotate AutoValue getters with JsonProperty to let Jackson know how a AutoValue attribute should map to a JSON attribute

Desrialization

  • Annotate AutoValue setters with JsonProperty to let Jackson know how a JSON attribute should map to a AutoValue attribute
  • Annotate JsonCreator if AutoValue uses create() method to create an instance
  • Annotate JsonDeserialize(builder = ...) if AutoValue uses Builder to create an instance

Other Resources

I’ve used this combination extensively in opensource project: stocktwits-java-api. More specificly, in the value package.

Thank you for reading!

Hope this guide helps!