Spring MVC with 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 the 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:
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();
}
}
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);
}
}
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.