javaspring-bootjackson

How can I define a custom ObjectMapper bean without overriding the one used by Spring Boot


I have a Spring Boot web app with several @RestController classes. I like the default json format returned by my REST controllers.

For use in my DAO beans (which do json serialization and deserialization), I have created a custom ObjectMapper:

@Configuration
public class Config{

  @Bean
  public ObjectMapper getCustomObjectMapper() {
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
    return objectMapper;
  }
}

And in each of my DAO classes I autowire my custom ObjectMapper:

@Repository
@Transactional
public class MyDaoImpl implements MyDao {

@Autowired
ObjectMapper objectMapper

//Dao implementation...

}

This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.

How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?


Solution

  • Since I didn't want to touch Spring's default ObjectMapper, creating a @Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.

    Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:

    @Component
    public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
    
    public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
    
    @Override
    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
        final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
            .genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
            .getBeanDefinition();
        // Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
        // as primary
        beanDefinition.setPrimary(false);
        final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
        beanDefinition.addQualifier(mapperQualifier);
        ((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
    }
    
    private ObjectMapper getCustomObjectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
        return objectMapper;
    }
    }
    

    As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
    My qualifier is an annotation which is annotated with @Qualifier:

    @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface PersistenceObjectMapper {
    }
    

    I can then autowire my custom ObjectMapper using my custom annotation, like this:

    @Repository
    public class MyDao {
    @Autowired
    public MyDao(DataSource dataSource, @PersistenceObjectMapper ObjectMapper objectMapper) {
    // constructor code
    }