Skip to content

Commit 6dbc828

Browse files
committed
MappingJackson2MessageConverter uses generic type
This is a backport of the #583201 minus use of GenericTypeResolver which in 5.0 has been refactored to provide a getJavaType method. Issue: SPR-16252
1 parent 3c31e03 commit 6dbc828

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.io.IOException;
2121
import java.io.StringWriter;
2222
import java.io.Writer;
23+
import java.lang.reflect.ParameterizedType;
2324
import java.lang.reflect.Type;
25+
import java.lang.reflect.TypeVariable;
2426
import java.nio.charset.Charset;
2527
import java.util.Arrays;
2628
import java.util.concurrent.atomic.AtomicReference;
@@ -35,8 +37,10 @@
3537
import com.fasterxml.jackson.databind.MapperFeature;
3638
import com.fasterxml.jackson.databind.ObjectMapper;
3739
import com.fasterxml.jackson.databind.SerializationFeature;
40+
import com.fasterxml.jackson.databind.type.TypeFactory;
3841

3942
import org.springframework.core.MethodParameter;
43+
import org.springframework.core.ResolvableType;
4044
import org.springframework.messaging.Message;
4145
import org.springframework.messaging.MessageHeaders;
4246
import org.springframework.util.Assert;
@@ -202,7 +206,7 @@ protected boolean supports(Class<?> clazz) {
202206

203207
@Override
204208
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
205-
JavaType javaType = this.objectMapper.constructType(targetClass);
209+
JavaType javaType = getJavaType(targetClass, conversionHint);
206210
Object payload = message.getPayload();
207211
Class<?> view = getSerializationView(conversionHint);
208212
// Note: in the view case, calling withType instead of forType for compatibility with Jackson <2.5
@@ -229,6 +233,81 @@ protected Object convertFromInternal(Message<?> message, Class<?> targetClass, O
229233
}
230234
}
231235

236+
private JavaType getJavaType(Class<?> targetClass, Object conversionHint) {
237+
if (conversionHint instanceof MethodParameter) {
238+
MethodParameter param = (MethodParameter) conversionHint;
239+
param = param.nestedIfOptional();
240+
Type genericParameterType = param.getNestedGenericParameterType();
241+
Class<?> contextClass = param.getContainingClass();
242+
Type type = getJavaType(genericParameterType, contextClass);
243+
return this.objectMapper.getTypeFactory().constructType(type);
244+
}
245+
return this.objectMapper.constructType(targetClass);
246+
}
247+
248+
private JavaType getJavaType(Type type, Class<?> contextClass) {
249+
TypeFactory typeFactory = this.objectMapper.getTypeFactory();
250+
if (contextClass != null) {
251+
ResolvableType resolvedType = ResolvableType.forType(type);
252+
if (type instanceof TypeVariable) {
253+
ResolvableType resolvedTypeVariable = resolveVariable(
254+
(TypeVariable<?>) type, ResolvableType.forClass(contextClass));
255+
if (resolvedTypeVariable != ResolvableType.NONE) {
256+
return typeFactory.constructType(resolvedTypeVariable.resolve());
257+
}
258+
}
259+
else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) {
260+
ParameterizedType parameterizedType = (ParameterizedType) type;
261+
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
262+
Type[] typeArguments = parameterizedType.getActualTypeArguments();
263+
for (int i = 0; i < typeArguments.length; i++) {
264+
Type typeArgument = typeArguments[i];
265+
if (typeArgument instanceof TypeVariable) {
266+
ResolvableType resolvedTypeArgument = resolveVariable(
267+
(TypeVariable<?>) typeArgument, ResolvableType.forClass(contextClass));
268+
if (resolvedTypeArgument != ResolvableType.NONE) {
269+
generics[i] = resolvedTypeArgument.resolve();
270+
}
271+
else {
272+
generics[i] = ResolvableType.forType(typeArgument).resolve();
273+
}
274+
}
275+
else {
276+
generics[i] = ResolvableType.forType(typeArgument).resolve();
277+
}
278+
}
279+
return typeFactory.constructType(ResolvableType.
280+
forClassWithGenerics(resolvedType.getRawClass(), generics).getType());
281+
}
282+
}
283+
return typeFactory.constructType(type);
284+
}
285+
286+
private ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
287+
ResolvableType resolvedType;
288+
if (contextType.hasGenerics()) {
289+
resolvedType = ResolvableType.forType(typeVariable, contextType);
290+
if (resolvedType.resolve() != null) {
291+
return resolvedType;
292+
}
293+
}
294+
295+
ResolvableType superType = contextType.getSuperType();
296+
if (superType != ResolvableType.NONE) {
297+
resolvedType = resolveVariable(typeVariable, superType);
298+
if (resolvedType.resolve() != null) {
299+
return resolvedType;
300+
}
301+
}
302+
for (ResolvableType ifc : contextType.getInterfaces()) {
303+
resolvedType = resolveVariable(typeVariable, ifc);
304+
if (resolvedType.resolve() != null) {
305+
return resolvedType;
306+
}
307+
}
308+
return ResolvableType.NONE;
309+
}
310+
232311
@Override
233312
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
234313
try {

spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import java.io.IOException;
2020
import java.lang.reflect.Method;
2121
import java.nio.charset.Charset;
22+
import java.nio.charset.StandardCharsets;
2223
import java.util.Arrays;
2324
import java.util.HashMap;
25+
import java.util.List;
2426
import java.util.Map;
2527

2628
import com.fasterxml.jackson.annotation.JsonView;
@@ -120,6 +122,20 @@ public void fromMessageValidJsonWithUnknownProperty() throws IOException {
120122
assertEquals("string", myBean.getString());
121123
}
122124

125+
@Test // SPR-16252
126+
public void fromMessageToList() throws Exception {
127+
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
128+
String payload = "[1, 2, 3, 4, 5, 6, 7, 8, 9]";
129+
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
130+
131+
Method method = getClass().getDeclaredMethod("handleList", List.class);
132+
MethodParameter param = new MethodParameter(method, 0);
133+
Object actual = converter.fromMessage(message, List.class, param);
134+
135+
assertNotNull(actual);
136+
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L), actual);
137+
}
138+
123139
@Test
124140
public void toMessage() throws Exception {
125141
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
@@ -209,8 +225,9 @@ public JacksonViewBean jsonViewResponse() {
209225
return bean;
210226
}
211227

212-
public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) {
213-
}
228+
public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) {}
229+
230+
void handleList(List<Long> payload) {}
214231

215232

216233
public static class MyBean {

0 commit comments

Comments
 (0)