12

I have a class set up to return a customised ObjectMapper. As far as I can find, the correct way to have Spring Boot use this ObjectMapper is to declare it as @Primary, which it is.

    @Configuration
    public class MyJacksonConfiguration {

        @Bean
        @Primary
        public ObjectMapper objectMapper() {
            return Jackson2ObjectMapperBuilder
                .json()
                .findModulesViaServiceLoader(true)
                .mixIn(Throwable.class, ThrowableMixin.class)
                .featuresToDisable(
                        WRITE_DATES_AS_TIMESTAMPS)
                .serializationInclusion(
                        Include.NON_ABSENT)
                .build();
        }
    }

However, when I return an object from a controller method it is serialized with the default Jackson ObjectMapper configuration.

If I add an explicit ObjectMapper to my controller and call writeValueAsString on it, I can see that this ObjectMapper is the customised one that I would like Spring Boot to use.

    @RestController
    public class TestController {

        @Autowired
        private TestService service;

        @Autowired
        private ObjectMapper mapper;

        @GetMapping(value = "/test", produces = "application/json")
        public TestResult getResult() {

            final TestResult ret = service.getResult();

            String test = "";
            try {
                test = mapper.writeValueAsString(ret);
                // test now contains the value I'd like returned by the controller!
            } catch (final JsonProcessingException e) {
                e.printStackTrace();
            }

            return ret;
        }
    }

When I run tests on my controller the test class also uses an autowired ObjectMapper. Again the ObjectMapper supplied to the test is the customised one.

So Spring knows about the customised ObjectMapper to some extent, but it isn't being used by my rest controller classes.

I have tried turning on Debug logging for Spring but can't see anything useful in logs.

Any idea what might be happening, or where else I should be looking to track down the issue?

EDIT: There appear to be multiple ways to do this, however the way I'm trying to do it appears to be a recommended method and I would like to get it to work this way - see 71.3 of https://docs.spring.io/spring-boot/docs/1.4.7.RELEASE/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper - am I misunderstanding something there?

Arturo Volpe
  • 3,442
  • 3
  • 25
  • 40
Dan
  • 7,446
  • 6
  • 32
  • 46

4 Answers4

8

Whilst the other answers show alternative ways of achieving the same result, the actual answer to this question is that I had defined a separate class that extended WebMvcConfigurationSupport. By doing that the WebMvcAutoConfiguration bean had been disabled and so the @Primary ObjectMapper was not picked up by Spring. (Look for @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) in the WebMvcAutoConfiguration source.)

Temporarily removing the class extending WebMvcConfigurationSupport allowed the @Primary ObjectMapper to be picked up and used as expected by Spring.

As I couldn't remove the WebMvcConfigurationSupport extending class permanently, I instead added the following to it:

@Autowired
private ObjectMapper mapper;

@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
    converters.add(new MappingJackson2HttpMessageConverter(mapper));
    addDefaultHttpMessageConverters(converters);
    super.configureMessageConverters(converters);
}
Dan
  • 7,446
  • 6
  • 32
  • 46
  • FYI this blog post (Customizing the Jackson ObjectMapper section) has really good info on this and notes the scenario that use of @EnableWebMvc disables the default Spring Boot auto configuration and give example how to configure in that case: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring#customizing-the-jackson-objectmapper – N. Ngo Mar 15 '19 at 19:10
  • For correct order, and to not broke String serialization we must keep same order. – therg Apr 08 '19 at 10:31
  • Similar to this, I found a rogue `@EnableWebMvc` annotation that was causing my ObjectMapper to not be used. – Mark Jul 02 '19 at 06:45
  • Thank you for giving the info on why it was breaking! Helped me solve my problem, even today! – Brent Thoenen Jan 07 '20 at 21:01
  • Our problem: a `@Configuration` class that `implements WebMvcConfigurer` that created a new `MappingJackson2HttpMessageConverter` without our custom `ObjectMapper`. This answer was key in tracking that down and likely saved me hours. The reason we create a new `MappingJackson2HttpMessageConverter` is it has to be in the first position (i.e. index 0) in `List>`. – Paul May 07 '22 at 19:14
3

Spring uses HttpMessageConverters to render @ResponseBody (or responses from @RestController). I think you need to override HttpMessageConverter. You can do that by extending WebMvcConfigurerAdapter and override following.

 @Override
public void configureMessageConverters(
  List<HttpMessageConverter<?>> converters) {     
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    super.configureMessageConverters(converters);
}

Spring documentation

lucid
  • 2,722
  • 1
  • 12
  • 24
0

Correct way that won't broke string serialization(keep order):

public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    addDefaultHttpMessageConverters(converters);

    HttpMessageConverter<?> jacksonConverter =
        converters.stream()
            .filter(converter -> converter.getClass().equals(MappingJackson2HttpMessageConverter.class))
            .findFirst().orElseThrow(RuntimeException::new);

    converters.add(converters.indexOf(jacksonConverter), new MappingJackson2HttpMessageConverter(objectMapper));
}
therg
  • 465
  • 5
  • 11
-1

Because what you have done is creating a bean that you can Autowired for later usage. (just like that you wrote in the code) Spring boot uses auto-configured Jackson2ObjectMapperBuilder to get default ObjectMapper for serialization.

So if you want to customize an ObjectMapper for serialization on your rest controller, you need to find a way to configure Jackson2ObjectMapperBuilder. There are two ways to do this:

Declare your own Jackson2ObjectMapperBuilder for spring

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    // other settings you want.
    return builder;
}

Setting through properties

You can set it through application.properties. For example, if you want to enable pretty print, set spring.jackson.serialization.indent_output=true.

For more detail information, please check here: Customize the Jackson ObjectMapper

S.Y. Wang
  • 224
  • 1
  • 8
  • @wang I think spring use HttpMessageConverter instead of deserializer. check this https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-responsebody-rendering – lucid Mar 21 '18 at 01:08
  • Indeed, Spring uses HttpMessageConverters to render ResponseBody. But to customize ResponseBody rendering is different from customizing ObjectMapper. According to the post, I think what he wants to do is to customize ObjectMapper but not MessageConverter. – S.Y. Wang Mar 21 '18 at 06:23
  • While both of the suggestions here appear to be correct according to the docs, I believe that what I'm trying is also a correct way of doing this according to the docs you linked to in your answer. Am I misunderstanding something? – Dan Mar 21 '18 at 09:59
  • @Dan according to https://stackoverflow.com/a/28327309/3599310 (Andy Wilkinson's comment). The way you do will not be applied to any `ObjectMapper` that configure by Spring Boot. That's the reason that why your `ObjectMapper` is not being used by rest controller. It just tells spring that 'If I autowired an ObjectMapper, use my version!' – S.Y. Wang Mar 21 '18 at 10:26
  • @S.Y.Wang thanks, given that Andy Wilkinson appears to be a Spring developer I guess he should know what he's talking about. In that case I really don't understand the docs - perhaps they are just wrong, or perhaps I'm still missing something. – Dan Mar 21 '18 at 10:31
  • @S.Y.Wang although Jonik's reply to Andy Wilkinson's comment suggests that Andy's comment is wrong... – Dan Mar 21 '18 at 10:38