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

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

  1. Vlad, I cannot replay to your comment-18566 – it does not have a REPLY link…

    Sorry, Vlad, I don’t understand what do you mean – what the generics I should try?
    I already use List for list of claims…

    1. Try using List . I don’t think it works without knowing the underlying object type. List alone is not enough.

      1. I guess I understand what’s happening – I wrote here List with diamond brackets but when I saved the comment – they disappear )

        Of cause I used the List gentrified with Claim object.

  2. Hello, Vlad!
    I used you lib “hibernate-types” in my Spring Boot project and setup ObjectMapperSupplier like this:

    public class HibernateTypesObjectMapperSupplier implements ObjectMapperSupplier {
    
        @Override
        public ObjectMapper get() {
            ObjectMapper objectMapper = new ObjectMapper()
                    .setTimeZone(TimeZone.getTimeZone("UTC"))
                    .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
                    .findAndRegisterModules();
            return objectMapper;
        }
    }```
    
    I tried to store to PostgreSQL database a like this entity:
    

    @Entity
    @TypeDef(name = “jsonb”, typeClass = JsonBinaryType.class)
    public class Profile {

    //...

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private List<Claim> claims;

    //...

    }

    <br />Where `Claim` is the base abstract class for other type of claims:
    
    @JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
    @JsonSubTypes({
            @Type(value = Claim1.class, name = "TYPE1"),
            @Type(value = Claim2.class, name = "TYPE2")
    })
    @Data
    public abstract class Claim implements Serializable {
        //...
    }
    
    When I test serialization and de-serialization of the `Profile` with the list of some types of `Claim` (using the same `ObjectMapper` from my `HibernateTypesObjectMapperSupplier`) without database all works as expected - in the json string I see a property `type` in the each claim:
    
    

    {

    “claims”: [
    {
    “type”: “TYPE1”,

    },
    {
    “type”: “TYPE2”,

    }
    ]
    }“`

    But when I tried to store Profile to the database then the type property has not been saved. And I can not read Profile objects from the DB because theirs claims lists cannot be de-serialized.

    Could you please help in this situation? How to correctly store json lists of inherited objects?

      1. Mmm… I used List<Claim>:

        @Entity
        @TypeDef(name = “jsonb”, typeClass = JsonBinaryType.class)
        public class Profile {
        
            //...
        
            @Type(type = "jsonb")
            @Column(columnDefinition = "jsonb")
            private List<Claim> claims;
        
            //...
        }
        

Leave a Reply

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