Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create a custom Jackson objectMapper for Spring without resorting to XML?

It's easy to create a custom ObjectMapper for Spring, but the configuration requires XML. I'm trying to reduce the amount of XML configuration for things that really aren't going to change without requiring a redeploy of my entire system anyway.

So the title says it all - can I use annotations or some other non-XML method to tell Spring, "Hey, please use my custom object mapper pls"?


EDIT:

This does not appear to work

@Configuration
@EnableWebMvc
public class AppConfig {

    @Primary
    @Bean
    public ObjectMapper mapper(){
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
        mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        mapper.registerModule(new JodaModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

EDIT 2: I do not believe that Spring is using my ObjectMapper. I have this code:

@Primary
@Bean
public ObjectMapper mapper(){
    ObjectMapper mapper = new ObjectMapper();
    JodaModule mod = new JodaModule();
    mod.addSerializer(DateTime.class, new JsonSerializer<DateTime>() {
        @Override
        public void serialize(DateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            System.out.println("Hi, bob");
        }
    });
    mapper.registerModule(mod);

    mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    return mapper;
}

but when I set a breakpoint on System.out.println("Hi, bob") it is never called - Though I'm definitely serializing a DateTime.

like image 363
Wayne Werner Avatar asked Mar 05 '15 14:03

Wayne Werner


People also ask

Does spring boot have Jackson ObjectMapper?

When using JSON format, Spring Boot will use an ObjectMapper instance to serialize responses and deserialize requests. In this article, we will take a look at the most common ways to configure the serialization and deserialization options.

How do I instantiate ObjectMapper?

Simplest usage is of form: final ObjectMapper mapper = new ObjectMapper(); // can use static singleton, inject: just make sure to reuse! MyValue value = new MyValue(); // ... and configure File newState = new File("my-stuff. json"); mapper.

Does RestTemplate use ObjectMapper?

So, by simply using the RestTemplateBuilder our RestTemplate will automatically use a MappingJackson2HttpMessageConverter configured with an ObjectMapper that uses the required ParameterNamesModule.

Should ObjectMapper be a bean?

Assuming no configuration is required, just a plain ObjectMapper will do. It's OK to be declared as static. But in environment like Spring, IMHO you should declare it as a bean. Spring will manage its lifecycle for you.


2 Answers

You can always follow the steps provided in the Spring Docs.

If you want to replace the default ObjectMapper completely, define a @Bean of that type and mark it as @Primary.

Defining a @Bean of type Jackson2ObjectMapperBuilder will allow you to customize both default ObjectMapper and XmlMapper (used in MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter respectively).

So, either you define a @Bean with your ObjectMapper like this:

@Primary
@Bean
public ObjectMapper mapper() {
    // Customize...
    return new ObjectMapper().setLocale(Locale.UK);
}

Or, you define a @Bean of type Jackson2ObjectMapperBuilder and customize the builder like this:

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();

    // Customize
    builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
    return builder;
}

This blog entry describes the customization further.

In order to register beans using the @Bean annotation you must declare the @Bean in a @Configuration class as described in the Spring docs about Java-based container configuration.

like image 150
wassgren Avatar answered Oct 21 '22 02:10

wassgren


This seems to be a bug. The Spring Boot documentation says that annotating an ObjectMapper Bean with @Primary should make the Spring context use it instead of the Spring's default mapper. However, this doesn't seem to work. I have found a workaround without using XML.

//Since Spring won't use the custom object mapper Bean defined below for
//HTTP message conversion(eg., when a Java object is returned from a controller,
//and should be converted to json using Jackson), we must override this method
//and tell it to use a custom message converter. We configure that custom converter
//below to use our customized Object mapper.
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(mappingJackson2HttpMessageConverter());
}

//configures the converter to use our custom ObjectMapper
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
    //this line here
    jsonConverter.setObjectMapper(objectMapper());
    return jsonConverter;
}


//Primary annotation tells the Spring container to use this
//mapper as the primary mapper, instead of
//the Spring's defaultly configured mapper. Primary annotation
// DOESN'T work for some reason(this is most likely a bug and will be resolved in the future.
// When resolved, this Bean will be all it takes to tell Spring to use this ObjectMapper everywhere)
// That means that there won't be a need to configure the Http message converters manually(see method above).
@Primary
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    configureObjectMapper(mapper);
    return mapper;
}

//configure ObjectMapper any way you'd like
//This configuration tells the ObjectMapper to 
//(de)serialize all fields(private,protected,public,..) of all objects
//and to NOT (de)serialize any properties(getters,setters).
private void configureObjectMapper(ObjectMapper mapper) {
    //properties for jackson are fields with getters and setters
    //sets all properties to NOT be serialized or deserialized
    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
    //tell the mapper to traverse all fields and not only default
    //default=public fields + fields with getters and setters
    //set all fields to be serialized and deserialized
    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
like image 44
Mate Šimović Avatar answered Oct 21 '22 04:10

Mate Šimović