How to customise the Jackson JSON mapper in Spring Web MVC

Spring MVC using annotations is great for AJAX + JSON until you need to customise the JSON mapping.

If you are using Spring 3.1 take a look at configuring a message converter as part of mvc namespace config for a more elegant solution.

There is an option to add annotations directly to your model classes, but if you want to keep them separate you can do the following:

1. Create a custom mapper class

In this example we are using joda-time's LocalDate, and want to serialize this to as a "yyyy-MM-dd" formatted String, instead of the provided 3 field mapping.


public class LocalDateSerializer extends SerializerBase<LocalDate> {

    public LocalDateSerializer() {
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        jgen.writeString(value.toString()); // this returns the date as "yyyy-MM-dd"
    }

    @Override
    public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
        throw new UnsupportedOperationException();
    }
}
    

2. Create a custom ObjectMapper

Create a custom ObjectMapper by extending the default Jackson ObjectMapper.


@Component
public class CustomObjectMapper extends ObjectMapper {
    public CustomObjectMapper() {
        CustomSerializerFactory sf = new CustomSerializerFactory();
        sf.addSpecificMapping(LocalDate.class, new LocalDateSerializer());
        this.setSerializerFactory(sf);
    }
}

3. Register classes in the Spring context

Register the new mapper at start up, by getting at the beans that were created by "<mvc:annotation-driven />"


@Component
public class JacksonFix {
    private AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter;
    private CustomObjectMapper objectMapper;

    @PostConstruct
    public void init() {
        HttpMessageConverter<?>[] messageConverters = annotationMethodHandlerAdapter.getMessageConverters();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            if (messageConverter instanceof MappingJacksonHttpMessageConverter) {
                MappingJacksonHttpMessageConverter m = (MappingJacksonHttpMessageConverter) messageConverter;
                m.setObjectMapper(objectMapper);
            }
        }
    }

    // this will exist due to the <mvc:annotation-driven/> bean
    @Autowired
    public void setAnnotationMethodHandlerAdapter(AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter) {
        this.annotationMethodHandlerAdapter  = annotationMethodHandlerAdapter;
    }

    @Autowired
    public void setObjectMapper(CustomObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
}

If you are using IntelliJ, you can add a suppress warning annotation at the top of the file:


@SuppressWarnings({"SpringJavaAutowiringInspection"})

Spring will set the 2 fields first, then run the init() method. It will 'dig' at the existing beans and find the Jackson converter, then replace the default object mapper with the custom object mapper.