5

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.

Community
  • 1
  • 1
Wayne Werner
  • 49,299
  • 29
  • 200
  • 290

2 Answers2

4

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.

Community
  • 1
  • 1
wassgren
  • 18,651
  • 6
  • 63
  • 77
  • I've added information to my question - I'm using the first example, but it does not appear to work. camelCase names are not changed to `camel_case`, and both `java.util.Date` and joda `DateTime` are displayed the normal way. – Wayne Werner Mar 06 '15 at 16:17
  • Is your @Configuration-class scanned at startup? – wassgren Mar 06 '15 at 17:01
  • I had to add some several lines of configuration to my web.xml - on my @Configuration class and added `@ComponentScan(basePackages = `com.mystuff`)`. It wasn't loading the page before but it does now so I assume it's being scanned. – Wayne Werner Mar 06 '15 at 17:40
  • set a breakpoint in my `mapper` function - it's definitely getting hit. – Wayne Werner Mar 06 '15 at 17:45
  • Will spring automatically use my ObjectMapper to convert the object, or do I have to specify that somewhere? – Wayne Werner Mar 06 '15 at 18:01
4

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);
}
Mate Šimović
  • 945
  • 11
  • 11