Improve support for generics in Jackson codecs

Closes gh-23791
This commit is contained in:
Rossen Stoyanchev 2020-01-23 12:31:00 +00:00
parent 77517d6cff
commit 992e75303d
3 changed files with 35 additions and 8 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -153,8 +153,10 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
private ObjectReader getObjectReader(ResolvableType elementType, @Nullable Map<String, Object> hints) {
Assert.notNull(elementType, "'elementType' must not be null");
MethodParameter param = getParameter(elementType);
Class<?> contextClass = (param != null ? param.getContainingClass() : null);
Class<?> contextClass = getContextClass(elementType);
if (contextClass == null && hints != null) {
contextClass = getContextClass((ResolvableType) hints.get(ACTUAL_TYPE_HINT));
}
JavaType javaType = getJavaType(elementType.getType(), contextClass);
Class<?> jsonView = (hints != null ? (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null);
return jsonView != null ?
@ -162,6 +164,12 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
getObjectMapper().readerFor(javaType);
}
@Nullable
private Class<?> getContextClass(@Nullable ResolvableType elementType) {
MethodParameter param = (elementType != null ? getParameter(elementType) : null);
return (param != null ? param.getContainingClass() : null);
}
private void logValue(@Nullable Object value, @Nullable Map<String, Object> hints) {
if (!Hints.isLoggingSuppressed(hints)) {
LogFormatUtils.traceDebug(logger, traceOn -> {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -34,6 +35,8 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Hints;
import org.springframework.http.HttpLogging;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
@ -55,6 +58,15 @@ public abstract class Jackson2CodecSupport {
*/
public static final String JSON_VIEW_HINT = Jackson2CodecSupport.class.getName() + ".jsonView";
/**
* The key for the hint to access the actual ResolvableType passed into
* {@link org.springframework.http.codec.HttpMessageReader#read(ResolvableType, ResolvableType, ServerHttpRequest, ServerHttpResponse, Map)}
* (server-side only). Currently set when the method argument has generics because
* in case of reactive types, use of {@code ResolvableType.getGeneric()} means no
* MethodParameter source and no knowledge of the containing class.
*/
static final String ACTUAL_TYPE_HINT = Jackson2CodecSupport.class.getName() + ".actualType";
private static final String JSON_VIEW_HINT_ERROR =
"@JsonView only supported for write hints with exactly 1 class argument: ";
@ -106,11 +118,20 @@ public abstract class Jackson2CodecSupport {
protected Map<String, Object> getHints(ResolvableType resolvableType) {
MethodParameter param = getParameter(resolvableType);
if (param != null) {
Map<String, Object> hints = null;
if (resolvableType.hasGenerics()) {
hints = new HashMap<>(2);
hints.put(ACTUAL_TYPE_HINT, resolvableType);
}
JsonView annotation = getAnnotation(param, JsonView.class);
if (annotation != null) {
Class<?>[] classes = annotation.value();
Assert.isTrue(classes.length == 1, JSON_VIEW_HINT_ERROR + param);
return Hints.from(JSON_VIEW_HINT, classes[0]);
hints = (hints != null ? hints : new HashMap<>(1));
hints.put(JSON_VIEW_HINT, classes[0]);
}
if (hints != null) {
return hints;
}
}
return Hints.none();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,6 @@ import javax.xml.bind.annotation.XmlRootElement;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.junit.jupiter.api.Disabled;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -522,7 +521,6 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq
assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2);
}
@Disabled
@ParameterizedHttpServerTest // gh-23791
public void personCreateViaDefaultMethodWithGenerics(HttpServer httpServer) throws Exception {
startServer(httpServer);