3

As I understood, Spring is already providing a bean for Jackson ObjectMapper. Therefore, instead of creating a new bean, I'm trying to customize this bean.

From this blog post, and then this Github project I used Jackson2ObjectMapperBuilder bean to achieve this customization.

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.findModulesViaServiceLoader(true);
    return builder;
}

Then, I was trying to customize the deserializer in order to make it lenient: if an exception is raised when deserializing a property, I want the result object's property to be null and let the deserialization continue (default is to fail on first property that cannot be deserialized).

I've been able to achieve that with a class NullableFieldsDeserializationProblemHandler that extends DeserializationProblemHandler (I do not think the code is relevant but if needed, I can share it).

The simplest way to register this handler is to use the .addHandler() method of ObjectMapper. But of course, doing like this, I would need to set that every time I inject and use the ObjectMapper. I'd like to be able to configure handler so that every time the ObjectMapper is auto-wired, the handler is already present.

The best solution I came up with so far is to use a @PostConstruct annotation only to register the problem handler.

@Configuration
public class JacksonConfiguration implements InitializingBean {

  @Autowired private ObjectMapper objectMapper;

  @Bean
  public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.findModulesViaServiceLoader(true);
    return builder;
  }

  @Override
  public void afterPropertiesSet() {
    objectMapper.addHandler(new NullableFieldsDeserializationProblemHandler());
  }
}

But the problem of this solution is that it seems I can still access an autowired ObjectMapper that doesn't have yet registered the problem handler (I can see it happening after when I need it in debug mode).

Any idea how I should register this handler? I've noticed Jackson2ObjectMapperBuilder has a .handlerInstantiator() but I couldn't figure out how to use it.

Note I've also tried with Jackson2ObjectMapperBuilderCustomizer since I'm using Spring Boot but had no better results.

woshilapin
  • 239
  • 4
  • 19

2 Answers2

4

It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. The handlerInstanciator() method is for something else.

However, it's possible to do so by registering a Jackson module:

  • the builder has a modules() method
  • the module has access via setupModule() to a SetupContext instance, which has a addDeserializationProblemHandler() method

This works:

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder builder) {
            builder.modules(new MyModule());
        }
    };
}

private static class MyModule extends SimpleModule {
    @Override
    public void setupModule(SetupContext context) {
        // Required, as documented in the Javadoc of SimpleModule
        super.setupModule(context);
        context.addDeserializationProblemHandler(new NullableFieldsDeserializationProblemHandler());
    } 
}
Frank Pavageau
  • 11,477
  • 1
  • 43
  • 53
  • It does do the trick also I'm not sure what would happen if I had to register multiple modules. Should I register the problem handler on each of them? And then what about the ones that are automatically register via `findModulesViaServiceLoader(true)`? Well, it raises some more questions but for the original question, I consider it solved. Thank you :-) – woshilapin Feb 27 '18 at 16:43
  • Modules are a way to configure a common `ObjectMapper`, which is where the `DeserializationProblemHandler` ends up : the `SetupContext` is a wrapper around the `ObjectMapper`, _not_ the `Module`. So there's no problem with multiple modules, as long as they don't try to set up different handlers (they usually don't anyway). – Frank Pavageau Feb 28 '18 at 08:46
  • Consider accepting the answer if it solves the problem, by the way :) – Frank Pavageau Feb 28 '18 at 08:47
  • Hi I tried this solution but the code flow is not coming inside `handleUnknownProperty`. I am using the code provided in this answer. Am I missing anything ? – vizsatiz Mar 13 '19 at 07:42
1

What about writing a bean like this:

@Configuration
public class ObjectMapperConfiguration {

    @Bean
    ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        // jackson 1.9 and before
        objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
        // or jackson 2.0
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }
}

This is for global configuration. If, instead, what you want to do is to configure the feature for specific a class, use this annotation above the class definition:

@JsonIgnoreProperties(ignoreUnknown = true)
NiVeR
  • 9,644
  • 4
  • 30
  • 35
  • Well, the whole point of _customizing_ the bean is about customizing, not create a new one. I'd like to customize the instance of Spring in order to get all modifications/customizations that Spring already did on the `ObjectMapper`. – woshilapin Feb 13 '18 at 20:41
  • This is `one instance` that Spring wil initialize once and use throughout the application. – NiVeR Feb 13 '18 at 21:25
  • Yes sure, it’s a bean. My point is, Spring already provide this bean. If I create one, I will end up with two and Spring might randomly choose one. Also, the one already provided might already be initialized and/or customized and I’d like to keep these customizations. – woshilapin Feb 13 '18 at 21:51