Adding Custom Serializers and Deserializers to Jackson’s ObjectMapper

Sometimes, the behavior of the Spring Data REST ObjectMapper (which has been specially configured to use intelligent serializers that can turn domain objects into links and back again) may not handle your domain model correctly. You can structure your data in so many ways that you may find your own domain model does not correctly translate to JSON. It is also sometimes not practical in these cases to support a complex domain model in a generic way. Sometimes, depending on the complexity, it is not even possible to offer a generic solution.

To accommodate the largest percentage of the use cases, Spring Data REST tries to render your object graph correctly. It tries to serialize unmanaged beans as normal POJOs, and tries to create links to managed beans where necessary. However, if your domain model does not easily lend itself to reading or writing plain JSON, you may want to configure Jackson’s ObjectMapper with your own custom type mappings and (de)serializers.

Abstract Class Registration

One key configuration point you might need to hook into is when you use an abstract class (or an interface) in your domain model. Jackson does not, by default, know what implementation to create for an interface. Consider the following example:

@Entity
public class MyEntity {

  @OneToMany
  private List<MyInterface> interfaces;
}

In a default configuration, Jackson has no idea what class to instantiate when POSTing new data to the exporter. You need to tell Jackson either through an annotation or, more cleanly, by registering a type mapping by using a Module.

Any Module bean declared within the scope of your ApplicationContext is picked up by the exporter and registered with its ObjectMapper. To add this special abstract class type mapping, you can create a Module bean and, in the setupModule method, add an appropriate TypeResolver, as follows:

public class MyCustomModule extends SimpleModule {

  private MyCustomModule() {
    super("MyCustomModule", new Version(1, 0, 0, "SNAPSHOT"));
  }

  @Override
  public void setupModule(SetupContext context) {
    context.addAbstractTypeResolver(
      new SimpleAbstractTypeResolver().addMapping(MyInterface.class,
        MyInterfaceImpl.class));
  }
}

Once you have access to the SetupContext object in your Module, you can do all sorts of cool things to configure Jackon’s JSON mapping. You can read more about how Modules work on Jackson’s wiki.

Adding Custom Serializers for Domain Types

If you want to serialize or deserialize a domain type in a special way, you can register your own implementations with Jackson’s ObjectMapper. Then the Spring Data REST exporter transparently handles those domain objects correctly.

To add serializers from your setupModule method implementation, you can do something like the following:

public class MyCustomModule extends SimpleModule {

  …

  @Override
  public void setupModule(SetupContext context) {

    SimpleSerializers serializers = new SimpleSerializers();
    SimpleDeserializers deserializers = new SimpleDeserializers();

    serializers.addSerializer(MyEntity.class, new MyEntitySerializer());
    deserializers.addDeserializer(MyEntity.class, new MyEntityDeserializer());

    context.addSerializers(serializers);
    context.addDeserializers(deserializers);
  }
}

Thanks to the custom module shown in the preceding example, Spring Data REST correctly handles your domain objects when they are too complex for the 80% generic use case that Spring Data REST tries to cover.