I have a spring rest web app that contains a a generic rest controller like below. The GET methods are working fine having Jackson serializing the objects to JSON. However when I try to call the save method, the RequestBody parameter is being converted to LinkedHashMap instead of the type defined by the T generic type.
@RestController
public abstract class CrudAPI<T extends Object, ID extends Serializable>{
@Transactional
@RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<Void> save(@RequestBody T entity){
service.save(entity);
return new ResponseEntity(HttpStatus.CREATED);
}
}
The JSON:
{
"id":null,
"version":null,
"name":"Copel",
"disabled":false,
"type":"P",
"labels":[
{
"id":null,
"version":null,
"name":"unidade consumidora"
}
]
}
I get the following error:
HTTP Status 500 - Request processing failed; nested exception is org.springframework.beans.NotReadablePropertyException: Invalid property 'version' of bean class [java.util.LinkedHashMap]: Could not find field for property during fallback access!
The Spring configuration:
@Configuration
@Import(Application.class)
@EnableWebMvc
@ComponentScan(basePackages = {"br.com.doc2cloud"})
public class WebConfig extends WebMvcConfigurerAdapter implements WebApplicationInitializer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
private MappingJackson2HttpMessageConverter jacksonConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setDateFormat(new ISO8601DateFormat());
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(mapper);
return jacksonConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonConverter());
super.configureMessageConverters(converters);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("javax.servlet.jsp.jstl.fmt.localizationContext", "messages");
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", characterEncodingFilter);
characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*");
}
}
What is wrong with my code?
I don't think you can achieve what you want. Java generics mechanism works only in compile-time. After compilation generic types are erased and are replaced by actual types (
Object
in your case). There is no way your controller will understand to what type you try to parse JSON data.The reason why you get
LinkedHashMap
is that Jackson uses it by default for "unknown" types. Typical JSON data is actually a key-value map and Jackson preserves attributes ordering - that's why linked hash map is used.Which version of Jackson are you using? I upgraded to 2.7.3 and when using generics (I have a base controller with common logic for saving, listing, etc) I had the same issue. Rolling back to 2.6.5 allowed me to continue using my generic base class. I haven't yet researched the reason for the issue but rolling back fixed it for me.