Search code examples
javaspringjacksonobjectmapperjackson-databind

Can't set ProblemHandler to ObjectMapper in Spring Boot


I tried to add custom problem handler to object mapper with Jackson2ObjectMapperBuilderCustomizer:

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder builder) {
            ObjectMapper m = builder.build();
            m.addHandler(
                    new DeserializationProblemHandler() {
                        @Override
                        public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException {
                            System.out.println("ahahahaa");
                            return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
                        }
                    }
            );
        }
    };
}

But when i autowired ObjectMapper bean _problemHandlers property is null.

I also tried just customize existed ObjectMapper with:

@Autowired
public customize(ObjectMapper mapper) {
...
}

But result is the same. I don't know who can erasure this property. I don't initialize another builders/factories/etc of object mapper in another place at all. What i'm doing wrong?


Solution

  • It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. Calling build() on the builder is a no-go, since the resulting ObjectMapper is local to the method: Spring itself will call build() later, creating another ObjectMapper instance.

    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 should then work

    @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 MyDeserializationProblemHandler());
        } 
    }
    
    private static class MyDeserializationProblemHandler extends DeserializationProblemHandler {
        @Override
        public boolean handleUnknownProperty(DeserializationContext ctxt,
                                             JsonParser p,
                                             JsonDeserializer<?> deserializer,
                                             Object beanOrClass,
                                             String propertyName)
                throws IOException {
            System.out.println("ahahahaa");
            return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
        }
    }
    

    EDIT (2022-06-27)

    As mentioned by E-Riz in the comments, with newer Spring Boot versions you can just register the module as a Spring Bean and it will be configured on the ObjectMapper with all the other modules.

    // Or declare it as a @Bean in a @Configuration
    @Component
    public class MyModule extends SimpleModule {
        @Override
        public void setupModule(SetupContext context) {
            // Required, as documented in the Javadoc of SimpleModule
            super.setupModule(context);
            context.addDeserializationProblemHandler(new MyDeserializationProblemHandler());
        } 
    
        private static class MyDeserializationProblemHandler extends DeserializationProblemHandler {
            @Override
            public boolean handleUnknownProperty(DeserializationContext ctxt,
                                                 JsonParser p,
                                                 JsonDeserializer<?> deserializer,
                                                 Object beanOrClass,
                                                 String propertyName)
                    throws IOException {
                System.out.println("ahahahaa");
                return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
            }
        }
    }