Can't set ProblemHandler to ObjectMapper in Spring Boot
Asked Answered
P

2

7

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?

Primordial answered 9/10, 2017 at 10:18 Comment(0)
G
9

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);
        }
    }
}
Gobbet answered 13/2, 2018 at 16:38 Comment(4)
Hi, I used your code. But control is not coming inside handleUnknownProperty when my request has an invalid JSON field. Am I missing something here ?Freethinker
context.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) maybe? It depends on what it's doing instead.Gobbet
This solution most likely has unintended consequences. That's because builder.modules() circumvents the auto-discovery of modules that Spring Boot does, so things like Java Time and other important modules on the classpath won't get registered. Check the JavaDoc of Jackson2ObjectMapperBuilder.modules() for more info.Seabrooke
With newer versions of Spring you don't need the customizer at all; any @Bean of type Module will automatically be registered with the ObjectMapper builder. That also avoids the nasty side effect I mentioned above; Spring Boot will still include auto-discovered modules it finds.Seabrooke
C
0

I am new to Spring Boot, then, it was difficult to understand how I could use this. But after some research I managed to figure out.

I had to create a class named src.main.java.com.applicationname.config.JacksonUnknownPropertyConfig.java with the contents:

package com.applicationname.config;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;

@SpringBootConfiguration
public class JacksonUnknownPropertyConfig {
  private static final Logger logger = LoggerFactory.getLogger(JacksonUnknownPropertyConfig.class);

  @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");

      final String missing = String.format("Unknown request property '%s'", propertyName);
      logger.warn(missing);

      if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, missing);
      }
      return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
    }
  }
}

And I also had to add FAIL_ON_UNKNOWN_PROPERTIES to the file src.main.resources.application.properties

spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=true

After this, looks like Sprint Boot automatically recognizes the new class I created and correctly loads it.

2020-07-20 23:41:22.934  INFO 16684 --- [         task-1] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-07-20 23:41:22.944 TRACE 16684 --- [         task-1] o.h.type.spi.TypeConfiguration$Scope     : Handling #sessionFactoryCreated from [org.hibernate.internal.SessionFactoryImpl@3edd0926] for TypeConfiguration
2020-07-20 23:41:22.946  INFO 16684 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-07-20 23:41:23.209  INFO 16684 --- [           main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-07-20 23:41:23.222  INFO 16684 --- [           main] c.a.p.AppointmentPublishingApplication   : Started AppointmentPublishingApplication in 6.445 seconds (JVM running for 7.615)
2020-07-20 23:41:26.229  INFO 16684 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-07-20 23:41:26.229  INFO 16684 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-07-20 23:41:26.236  INFO 16684 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 7 ms
ahahahaa

Related threads:

  1. Spring Boot, Spring MVC JSON RequestBody: Unknown property ignored
  2. How to customise Jackson in Spring Boot 1.4
  3. jackson Unrecognized field
  4. Ignoring new fields on JSON objects using Jackson
  5. Configure FAIL_ON_UNKNOWN_PROPERTIES for each RequestMapping differently in the Controller
  6. Jackson deserializer priority?
  7. Spring Data Elastic - Java.Time.Instant class Jackson deserliization not working
  8. Enable Jackson Deserialization of Empty Objects to Null
  9. Spring RestController : reject request with unknown fields
  10. How do you globally set Jackson to ignore unknown properties within Spring?
  11. https://www.baeldung.com/spring-bean
  12. https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-spring-mvc
Canthus answered 21/7, 2020 at 2:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.