Friday, February 28, 2014

using jackson mixins and modules to fix serialization issues on external classes

the problem

Let's say you have some classes coming from another library that you need to serialize into JSON using Jackson. You can't manipulate the source code of these classes one reason or another, and you have a problem:

These classes don't serialize correctly

There are a number of reasons this can happen, but in this post we're going to focus on two examples: two classes that have a recursive relationship to one another, and a class that doesn't conform to the bean spec.

dealing with recursive relationships

Let's say we have two classes, User and Thing, as shown below. User has a one to many relationship with Thing, and Thing has a many to one relationship back to its parent, User:

Given these classes, let's say in a unit test we create users using the following code, establishing the recursive relationship:

Now that we can create a user, let's try serializing:

If you run that method, your test will fail, and you'll see a nice CPU spike on your computer because this just happened:

fixing recursive relationship issues with mixins

As it turns out, Jackson has an awesome feature called mixins that can address this type of problem (remember, we're assuming User and Thing are not modifiable).

Mixins allow you to create another class that has additional Jackson annotations for special serialization handling, and Jackson will allow you to map that class to the class you want the annotations to apply to. Let's create a mixin for Thing that specifies a @JsonFilter:

Now you might be thinking, "What's that 'thing filter' string referencing?" We have to add this mixin to the object mapper, binding it to the Thing class, and then we have to create a filter called "thing filter" that exludes Thing's field of user, as shown in the test below:

If you run this test, you'll see that it passes.

dealing with a class that doesn't conform to the bean spec

Let's say we have two other classes, Widget and WidgetName. For some reason, the person who wrote WidgetName decided to not conform to the bean spec, meaning when we serialize an instance of Widget, we can't see data in WidgetName:

Let's say we're creating widgets using the code below:

If we try to create a widget like this and serialize it, we won't see the name. Here's a test that will serialize:

And here's the output:

fixing classes that don't conform to the bean spec with modules and customer serializers

We can address this problem pretty easily using a Jackson module that provides a custom serializer for WidgetName. The serializer can be seen below, and it uses the JsonGenerator instance to write the value from the WidgetName argument:

We now need to wire this up in order to use it. Below is a test that creates a SimpleModule instance, wires up our custom serializer to it, and registers the module within our ObjectMapper instance:

If you run this test, you can see the correct output for the serialization:

conclusion and resources

All the resources used in this example can be found on Github at https://github.com/theotherian/jackson-mixins-and-modules. If you end up having to serialize data that's outside of your control and is causing you problems, you should be able to get a lot of mileage out of these two solutions.

1 comment: