Looking at the Jackson Serializer post, it does introduce a particular code smell as it uses if statements to check the type of object and take a different action based on that type. Normally, that is indicative of the need to implement the action in the class and/or subclasses. However, this is serialization and we certainly don’t want to put any serialization code in the model classes themselves. This article looks at getting rid of that code smell using inheritance with serializers. The source code is available in my blog post code repository.

We can create a class hierarchy of serializers that makes use of inheritance to replace a set of if/then statements based on the object type. In this post, we’ll implement serializers that are inherited for the different Pet types and ensure that the serialization of each Pet type of encapsulated in its own serializer.

One problem is that the execution of overridden implementations is sequential, we would execute the implementation in the parent class, then the first subclass, and then any further subclasses. Since our top level implementation likely consists of :

  • Start writing an object “{“
  • Write Object properties “x = 1”
  • End writing an object “}”

If we subclass the serializer and override the serialize() method and call the parent implementation, in JSON we will open the object, close it, and then write any subclass properties. We want to open the object, write all of the properties, including subclass properties, and then close the object.

Template Method

We can solve this problem using a template method which is invoked during the execution of an algorithm, and provides options to extend the algorithm while maintaining the main structure of it. This is a good technique to use when you want to maintain a rigid list of actions, but allow one of those actions to be overridden and extended.

We’ll create an ObjectSerializer class that is used as the base class for our serializers. This class expects subclasses to be used to serialize objects.

public abstract class ObjectSerializer<T> extends StdSerializer<T> {

  public ObjectSerializer(final Class<T> t) {
    super(t);
  }

  @Override
  public void serialize(final T value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
    gen.writeStartObject();
    doWriteProperties(value, gen, provider);
    gen.writeEndObject();
  }

  protected abstract void doWriteProperties(final T value, final JsonGenerator gen, final SerializerProvider provider);
}

Our serialize method starts a new object, invokes the call to write the object properties, and then ends the object. Any properties written in the doWriteProperties method will sit inside the JSON object.

Our PetSerializer won’t write any Pet, but could just write the common properties for the Pet object :

public class PetSerializer<T extends Pet> extends ObjectSerializer<T> {
  public PetSerializer(final Class<T> clazz) {
    super(clazz);
  }

  @Override
  protected void doWriteProperties(final T value, final JsonGenerator gen, final SerializerProvider provider)
      throws IOException {
    // this could go in the subclass serializer if a more calculated value was needed.
    gen.writeStringField("type", value.getClass().getSimpleName());
    gen.writeStringField("name", value.getName());
  }
}

Here we override the doWriteProperties method to write the type and the name, the two common values of the Pet type that need serializing. Note that we also enhanced the generic type to ensure that we only use this on subtypes of Pet.

We can now subclass this serializer for each of our Pet types :

public class DogSerializer extends PetSerializer<Dog> {
  public DogSerializer() {
    super(Dog.class);
  }
  @Override
  protected void doWriteProperties(final Dog value, final JsonGenerator gen, final SerializerProvider provider)
      throws IOException {
    super.doWriteProperties(value, gen, provider);
    gen.writeStringField("size", value.getSize().toString());
  }
}

and

public class SnakeSerializer extends PetSerializer<Snake> {

  public SnakeSerializer() {
    super(Snake.class);
  }

  @Override
  protected void doWriteProperties(final Snake value, final JsonGenerator gen, final SerializerProvider provider)
      throws IOException {
    super.doWriteProperties(value, gen, provider);
    gen.writeNumberField("length", value.getLength());
  }
}

We have implemented the PetSerializer for our two classes and override the doWriteProperties method to write properties specific to that Pet.

To use our new serializers, we just need to register them with the Jackson Module in our main code. Note that we no longer need to register the Pet serializer since it is never used directly, only from their subclasses. We do however have to register the serializers for the Dog and Snake classes.

public class Main {

  public static void main(final String[] args) throws JsonProcessingException {
    final ObjectMapper objectMapper = new ObjectMapper();
    final SimpleModule module = new SimpleModule("mySerializers");
    module.addSerializer(new PersonSerializer());
    module.addSerializer(new DogSerializer());
    module.addSerializer(new SnakeSerializer());
    objectMapper.registerModule(module);
    ...
    ...
  }
}

The rest of the code to create the model and write it to JSON will now still work with the new serializers used to write the objects. While this does get rid of the code smell mentioned originally, technically, it is only moving it to a hidden location. Jackson still does some kind of lookup for the class to determine what serializer it should use instead of a set of if statements to locate the matching serializer.