How do I obtain the Jackson ObjectMapper in use by Spring 4.1?
Asked Answered
C

8

95

Spring 4.1 instantiates a Jackson ObjectMapper instance. I have reason to want to @Autowire that instance into one of my controllers: The controller does some minor JSON parsing of its own using Jackson, but the ObjectMapper it uses should be the one and same instance that Spring itself is using. How do I go about accomplishing that?

Note that I'm not asking how to custom configure the ObjectMapper in use by Spring; I'm happy with the defaults. I just want to fish the instance used by Spring out so that I can re-use the existing instance in my own code.

Canvass answered 5/5, 2015 at 17:56 Comment(3)
Are you sure it creates a bean that can be Autowired and not local instances of ObjectMapper? If yes, can't this be fetched from the 'load bean from Context of type ObjectMapper.class'?Raincoat
Adding an @Autowire property of type ObjectMapper in the controller is not enough. Apparently, Spring does not expose it as a standard bean.Canvass
In test environment I get it by adding @Autowired on ObjectMapper and @JsonTest on test classOrgiastic
H
67

If you're using Spring Boot with Jackson on your classpath and default implementation for JSON parsing in your REST controller, then this should work:

@Autowired
private ObjectMapper jacksonObjectMapper;
Hypostasize answered 9/6, 2015 at 7:25 Comment(5)
Doesn't work. Tested with Spring-boot 1.4.0.M2 which uses spring 4.3.0.RC1 and throws NoSuchBeanDefinitionExceptionResponser
First: works with spring boot 1.5.3 when autowiring in a component. Second: WARNING: This is one of the most dangerous ideas i've had so far. Because of its singleton-nature, you could get in very ugly problems using the Spring Objectmapper as an @Autowired bean, e.g. no Object in your Controllers is serialized anymore, because you changed a serializationfeature (unwrap_root_value, anyone?) in ANY class using the autowired mapper. So please: JUST use it when you are sure you just need the ootb-features of the mapper. For EVERY other usecase, extend it and create your own custom mapperMusclebound
@Musclebound is it safe if I only use the Springs autowired Object Mapper to 'writeValueAsString'? I am working on transaction logging and want to capture the output exactly how it is returned to the user.Cyanide
@Responser A common problem that I have had many times is importing by mistake org.codehaus.jackson.map.ObjectMapper instead of com.fasterxml.jackson.databind.ObjectMapper which is the package Spring Boot actually create a bean from. Changing this, autowired used to work for me.Moneymaker
Is it thread safe?Cayuga
V
30

As was said by others, you can't @Autowired it in directly into your controller.

@Emerson Farrugia's suggestion to create a new instance using

Jackson2ObjectMapperBuilder.json().build()

also didn't work for me because the obtained instance was not following the spring.jackson.* configuration properties, which I needed it to.


The solution I found was to obtain the ObjectMapper from Spring's MappingJackson2HttpMessageConverter which is injectable.

So I autowired it:

@Autowired
private MappingJackson2HttpMessageConverter springMvcJacksonConverter;

and then get the ObjectMapper from it like this:

ObjectMapper objectMapper = springMvcJacksonConverter.getObjectMapper();

This instance behaves exactly as Spring MVC's own message conversion - it probably is the same instance anyway.

Verdi answered 18/4, 2017 at 14:20 Comment(3)
MappingJackson2HttpMessageConverter is not injectable :(Gardener
Injectable but the objectmapper is null, so NPE.Minicam
MappingJackson2HttpMessageConverter doesn't seem to be injectable in plain, non-Boot Spring either.Hex
H
24

If you take a look at MappingJackson2HttpMessageConverter, you'll see that it creates a new ObjectMapper, but doesn't expose it as a bean. There's a getter, but the only way I've fished it out in the past is when I created the MappingJackson2HttpMessageConverter myself, e.g.

public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        MappingJackson2HttpMessageConverter jacksonMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = jacksonMessageConverter.getObjectMapper();

        objectMapper.registerModule(new JodaModule());
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);

        converters.add(jacksonMessageConverter);
    }
}

If you're working with Spring Boot, there's a section in the manual dedicated to working with the ObjectMapper If you create a default Jackson2ObjectMapperBuilder @Bean, you should be able to autowire that same ObjectMapper instance in your controller.

Hauberk answered 5/5, 2015 at 18:17 Comment(7)
Right, I've encountered this path but it involves re-creating the default Jackson settings that Spring creates. Which I don't want to have to do.Canvass
FWIW, Spring creates more than one ObjectMapper. The JSON message converter creates one, as does the XML converter. Creating a third that's identical to the JSON message converter is as simple as Jackson2ObjectMapperBuilder.json().build(). But if you really need it to be the same instance, call that line in a @Bean and wire it in wherever you need it. Spring will take care of wiring it into the converters.Hauberk
Why the heck spring uses different instances of OM. This is so confusing. Spring Boot documentation says that spring.jackson.serialization.indent_output=true is just the only thing that you need to have web response prettified. But it really just changes the bean, and has no effect on the message converted.Parke
You need to add @Configuration on the class declaration for this to workRummer
The doc says, "If you want to replace the default ObjectMapper completely, either define a @Bean of that type and mark it as @Primary or, if you prefer the builder-based approach, define a Jackson2ObjectMapperBuilder @Bean. Note that, in either case, doing so disables all auto-configuration of the ObjectMapper." So, creating the @Bean means overwriting all settings in yml/properties files. What if I want to get the configured objectmapper? The Jackson2ObjectMapperBuilder with @Autowired always return null.Minicam
Since Spring 3.1 the WebMvcConfigurerAdapter is deprecated, so just use the WebMvcConfigurer interface directlyDecontrol
@Decontrol it's been deprecated since 5.0, not 3.1.Hauberk
W
17

The ObjectMapper is created by Jackson2ObjectMapperBuilder, and you can inject the builder using:

@Autowired
private Jackson2ObjectMapperBuilder mapperBuilder;

Then use mapperBuilder.build() to build an ObjectMapper instance, and this instance can use configurations in application.properties. Official doc here.

Willamina answered 29/12, 2018 at 7:39 Comment(2)
Trying to work out how I'd then take that new builder and "install" it into Spring MVC default message conversion... (and I'm plain Spring MVC, not Spring Boot).Sterigma
Alternatively, the builder can configure an existing mapper with mapperBuilder.configure(mapper). This is very useful in Kotlin, where you probably want to get the mapper instance from jackson-module-kotlin's jacksonObjectMapper(), for better Kotlin support.Mindymine
M
10

I have debugged into source code of Spring Boot and found that only when we launch the whole context Jackson2ObjectMapperBuilder will contain the config we put in application.yml.

This means, if we want to generate an ObjectMapper in a Spring Boot test, with JUnit 5, we have to:

@ExtendWith(SpringExtension.class)
@SpringBootTest
class SomeTest {
    @Autowired
    private Jackson2ObjectMapperBuilder builder;
    ...

    @Test
    void testObjectMapper() {
        ObjectMapper mapper = builder.build();


    }

We cannot only make @SpringBootTest(classes = Jackson2ObjectMapperBuilder.class) to generate this builder.

When we bootRun we don't have this problem.

The configuration is set in Jackson2ObjectMapperBuilderCustomizerConfiguration#customize(Jackson2ObjectMapperBuilder builder) method.

enter image description here

Minicam answered 2/8, 2019 at 9:1 Comment(2)
I think @ExtendWith(SpringExtension.class) is implicitly included by @SpringBootTestMindymine
You can use the '@JsonTest' test slice to get the configured mapper without launching the entire app like '@SpringBootTest' does. Much faster. See docs.spring.io/spring-boot/docs/current/reference/html/…Mindymine
R
1

When you try to @Autowire the MappingJackson2HttpMessageConverter it gives you: No qualifying bean of type 'org.springframework.http.converter.json.MappingJackson2HttpMessageConverter' available: expected single matching bean but found 4: mappingJackson2HttpMessageConverter,jacksonHttpMessageConverter,halJacksonHttpMessageConverter,alpsJsonHttpMessageConverter.

This is not a big issue, you can just change your variable name to one of the above to get that instance: @Autowired private MappingJackson2HttpMessageConverter halJacksonHttpMessageConverter;

Reconstructionist answered 18/6, 2018 at 10:7 Comment(0)
M
1

An Update for Spring 4.3:

  • As of Spring 4.3, classes with a single constructor can omit the @Autowired annotation
  • Consequently, if you add a constructor which takes an ObjectMapper parameter it will be autowired with the Jackson ObjectMapper instance without needing to use the annotation @Autowired
Mckim answered 5/4, 2022 at 16:29 Comment(0)
J
-3

A two-stepper if you will;

  1. At your @SpringBootApplication class, add:

    @Bean
    public ObjectMapper mapper() {
      return new ObjectMapper();
    }
    
  2. Anywhere you wish to use ObjectMapper:

    @Autowired
    ObjectMapper mapper;
    

Peace!

Jac answered 15/6, 2018 at 16:43 Comment(1)
This would provide an ObjectMapper instance, but not the same one used by Spring. Any custom Jackson configurations added in your application config will not exist here unless you set your configurations up in 2 places.Cyanide

© 2022 - 2024 — McMap. All rights reserved.