Advanced Object Serialization Using Jackson

Posted by n3integration on October 4, 2015

I am a long time fan of Gson for object to JSON serialization. If you have a fairly trivial object graph, it is excellent in terms of simplicity and being non-intrusive.

Gson gson = new Gson();
Status out = new Status("ok");

String json = gson.toJson(out);
Status in = gson.fromJson(json, Status.class);

However, once you introduce interfaces and polymorphism, object serialization tends to get more complicated.

After having moved to the Play! framework in recent years, the Jackson serialization library comes preinstalled along with an array of additional dependencies. Jackson has long been praised for being a high-performance serialization framework. Coupled with the Play! framework’s Json utility class make it easy to get up and running with Jackson.

Polymorphism

As mentioned above, interfaces can be problematic when (de)serializing an object graph. This is fairly trivial with Jackson. Jackson provides annotations to overcome this situation. Although it is not perfect, it is much simpler than some of the alternatives that I’ve come across in the past.

Say for example that you have declared an interface named Widget and you’ve declared three implementations Box, Ball and Other. You will need to annotate the Widget interface with the @JsonTypeInfo annotation. For example:

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.PROPERTY,
  defaultImpl = Other.class,
  property = "type"
)
@JsonSubTypes({
  @JsonSubTypes.Type(value = Box.class, name = "box"),
  @JsonSubTypes.Type(value = Ball.class, name = "ball"),
})
public interface Widget {
  default String getType() {
    return getClass().getSimpleName().toLowerCase();
  }
}

We’ve accomplished a few things in the code snippet above:

  1. Annotated our interface with the type property as the property that determines which class is (de)serialized.
  2. Identified which implementation to use based on the value of the property. Note that there is a code smell here. Ideally the interface should not be aware of its implementations. As written, this requires that the codebase be updated in two places for every new implementation of the interface.
  3. The interface has a default implementation that uses the lowercase simple name of the implementation class. The benefit being that this provides a language agnostic approach for clients other than Java.

Dependencies

An additional challenge that occurs when serializing complex object graphs is with an object’s dependencies (i.e. one to many or many to many). Again, this is fairly trivial with Jackson’s annotations. Using the example above, let’s say that we have a Factory class that produces Widgets. We’ll need to declare Widgets as a managed dependency.

public class Factory {
  @JsonManagedReference("factory-widgets")
  private List<Widget> widgets;
}

In order to resolve the Factory that produces each Widget we’ll need to declare Factory as a back reference.

...
public interface Widget {
  ...
  @JsonBackReference("factory-widgets")
  public void setFactory();

  @JsonBackReference("factory-widgets")
  public Factory getFactory();
}

Without these annotations in place, you will receive a StackOverflowException with a cryptic error message.