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:
- Annotated our interface with the
type
property as the property that determines which class is (de)serialized. - 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.
- 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.