How to customize the Jackson ObjectMapper used by Hibernate-Types

(Last Updated On: March 27, 2018)

Introduction

As already explained, the hibernate-types open-source project allows you to map JSON, ARRAY when using JPA and Hibernate.

Since I launched this project, one of the most wanted feature was to add support for customizing the underlying Jackson ObjectMappper, and since version 2.1.1, this can be done either declaratively or programmatically.

In this article, you are going to see how do customize the ObjectMappper when using the hibernate-types project.

Declarative configuration

The easiest way to achieve this goal is to set the hibernate.types.jackson.object.mapper configuration property in the hibernate.properties file:

hibernate.types.jackson.object.mapper=com.vladmihalcea.hibernate.type.json.loader.CustomObjectMapperSupplier

This property takes the fully-qualified class name of an implementation of the ObjectMapperSupplier interface:

What’s nice about customizing the hibernate-types project is that you can use either the Hibernate-specific hibernate.properties or you can provide a hibernate-types.properties file if you cannot alter hibernate.properties.

You can even supply a different Java properties file via the hibernate-types.properties.path System property.

The CustomObjectMapperSupplier can look as follows:

public class CustomObjectMapperSupplier 
    implements ObjectMapperSupplier {

    @Override
    public ObjectMapper get() {
        ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
        
        objectMapper.setTimeZone(
            TimeZone.getTimeZone("GMT")
        );
        SimpleModule simpleModule = new SimpleModule(
            "SimpleModule", 
            new Version(1, 0, 0, null, null, null)
        );
        simpleModule.addSerializer(new MoneySerializer());
        objectMapper.registerModule(simpleModule);
        
        return objectMapper;
    }
}

Here, we are using a custom ObjectMapper that uses our own MoneySerializer for handling BigDecimal Java Object types:

public class MoneySerializer 
    extends JsonSerializer<BigDecimal> {

    @Override
    public void serialize(
                BigDecimal value, 
                JsonGenerator jsonGenerator, 
                SerializerProvider provider)
            throws IOException {
        jsonGenerator.writeString(
            value.setScale(2, BigDecimal.ROUND_HALF_UP).toString()
        );
    }

    @Override
    public Class<BigDecimal> handledType() {
        return BigDecimal.class;
    }
}

So, if you are persisting the following Book entity which has a JSON properties attribute:

Book book = new Book();
book.setIsbn("978-9730228236");
book.setProperties(
    JacksonUtil.toJsonNode(
        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.991234567" + 
        "}"
    )
);

entityManager.persist(book);

When fetching the Book entity, we can see that the BigDecimal takes only two decimals:

Book book = entityManager
.unwrap(Session.class)
.bySimpleNaturalId(Book.class)
.load("978-9730228236");

assertEquals(
    "44.99", 
    book.getProperties().get("price").asText()
);

Programmatic configuration

For even more flexibility, you can use the programmatic configuration.

Assuming we have the following Object type that we want to serialize as JSON:

public class Location 
    implements Serializable {

    private String country;

    private String city;

    private BigDecimal reference;

    //Getters and setters omitted for brevity
}

And, we have an Event entity that maps the Location Java Object to a jsonb PostgreSQL column type:

@Entity(name = "Event")
@Table(name = "event")
public class Event {

    @Id
    private Long id;

    @Type(type = "location")
    @Column(columnDefinition = "jsonb")
    private Location location;

    //Getters and setters omitted for brevity
}

We can use a custom ObjectMapper as follows:

JsonBinaryType jsonBinaryType = new JsonBinaryType(
    new CustomObjectMapperSupplier().get(), 
    Location.class
);

properties.put("hibernate.type_contributors",
    (TypeContributorList) () -> Collections.singletonList(
        (typeContributions, serviceRegistry) ->
            typeContributions.contributeType(
                jsonBinaryType, "location"
            )
    )
);

The hibernate.type_contributors configuration property allows you to register custom Hibernate Types, and we can use this feature to provide our own custom ObjectMapper to the JsonBinaryType provided by the hibernate-types project.

So, if we persist the following Event entity:

Location location = new Location();
location.setCountry("Romania");
location.setCity("Cluj-Napoca");
location.setReference(
    BigDecimal.valueOf(2.25262562526626D)
);

Event event = new Event();
event.setId(1L);
event.setLocation(location);

entityManager.persist(event);

When fetching the Event entity, we can see that the BigDecimal takes only two decimals:

Event event = entityManager.find(Event.class, 1L);

assertEquals("2.25", event.getLocation().getReference().toString());

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Not only that you can customize the ObjectMapper when using the hibernate-types project, but the new configuration mechanism will allow for customizing other behaviors as well.

So, stay tuned for more!

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

3 thoughts on “How to customize the Jackson ObjectMapper used by Hibernate-Types

    1. The only way to fix this issue is to change Hibernate ORM to allow it to pass the configuration properties in the Type constructor.

      1. Thank you. I just wanted to write it here to help who uses Spring Boot. Your work and your posts are great.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.