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 an 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.