diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 546aca46989..2d3dd80e2f2 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -17,6 +17,7 @@ package org.springframework.http.converter.json; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.nio.charset.Charset; @@ -311,11 +312,37 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener */ protected JavaType getJavaType(Type type, Class contextClass) { TypeFactory typeFactory = this.objectMapper.getTypeFactory(); - if (type instanceof TypeVariable && contextClass != null) { - ResolvableType resolvedType = resolveVariable( - (TypeVariable) type, ResolvableType.forClass(contextClass)); - if (resolvedType != ResolvableType.NONE) { - return typeFactory.constructType(resolvedType.resolve()); + if (contextClass != null) { + ResolvableType resolvedType = ResolvableType.forType(type); + if (type instanceof TypeVariable) { + ResolvableType resolvedTypeVariable = resolveVariable( + (TypeVariable) type, ResolvableType.forClass(contextClass)); + if (resolvedTypeVariable != ResolvableType.NONE) { + return typeFactory.constructType(resolvedTypeVariable.resolve()); + } + } + else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class[] generics = new Class[parameterizedType.getActualTypeArguments().length]; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeArguments.length; i++) { + Type typeArgument = typeArguments[i]; + if (typeArgument instanceof TypeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable( + (TypeVariable) typeArgument, ResolvableType.forClass(contextClass)); + if (resolvedTypeArgument != ResolvableType.NONE) { + generics[i] = resolvedTypeArgument.resolve(); + } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + return typeFactory.constructType(ResolvableType. + forClassWithGenerics(resolvedType.getRawClass(), generics).getType()); } } return typeFactory.constructType(type); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index c156cbd6bb9..866b37d1ad6 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -240,6 +240,29 @@ public class RequestResponseBodyMethodProcessorTests { assertEquals("Jad", result.getName()); } + @Test // SPR-14470 + public void resolveParameterizedWithTypeVariableArgument() throws Exception { + Method method = MyParameterizedControllerWithList.class.getMethod("handleDto", List.class); + HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); + MethodParameter methodParam = handlerMethod.getMethodParameters()[0]; + + String content = "[{\"name\" : \"Jad\"}, {\"name\" : \"Robert\"}]"; + this.servletRequest.setContent(content.getBytes("UTF-8")); + this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE); + + List> converters = new ArrayList<>(); + converters.add(new MappingJackson2HttpMessageConverter()); + RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters); + + @SuppressWarnings("unchecked") + List result = (List) + processor.resolveArgument(methodParam, container, request, factory); + + assertNotNull(result); + assertEquals("Jad", result.get(0).getName()); + assertEquals("Robert", result.get(1).getName()); + } + @Test // SPR-11225 public void resolveArgumentTypeVariableWithNonGenericConverter() throws Exception { Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class); @@ -725,6 +748,17 @@ public class RequestResponseBodyMethodProcessorTests { void setId(Long id); } + @SuppressWarnings("unused") + private static abstract class MyParameterizedControllerWithList { + + public void handleDto(@RequestBody List dto) { + } + } + + @SuppressWarnings("unused") + private static class MySimpleParameterizedControllerWithList extends MyParameterizedControllerWithList { + } + @SuppressWarnings({ "serial" }) private static class SimpleBean implements Identifiable {