Add Kotlin body advices
This commit introduces KotlinRequestBodyAdvice and KotlinResponseBodyAdvice in order to set a KType hint when relevant. Closes gh-34923
This commit is contained in:
@@ -17,27 +17,20 @@
|
||||
package org.springframework.http.converter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KType;
|
||||
import kotlin.reflect.full.KCallables;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import kotlinx.serialization.KSerializer;
|
||||
import kotlinx.serialization.SerialFormat;
|
||||
import kotlinx.serialization.SerializersKt;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
|
||||
@@ -84,12 +77,12 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return serializer(ResolvableType.forClass(clazz)) != null;
|
||||
return serializer(ResolvableType.forClass(clazz), null) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) {
|
||||
if (!ResolvableType.NONE.equals(type) && serializer(type) != null) {
|
||||
if (!ResolvableType.NONE.equals(type) && serializer(type, null) != null) {
|
||||
return canRead(mediaType);
|
||||
}
|
||||
else {
|
||||
@@ -99,7 +92,7 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
|
||||
@Override
|
||||
public boolean canWrite(ResolvableType type, Class<?> clazz, @Nullable MediaType mediaType) {
|
||||
if (!ResolvableType.NONE.equals(type) && serializer(type) != null) {
|
||||
if (!ResolvableType.NONE.equals(type) && serializer(type, null) != null) {
|
||||
return canWrite(mediaType);
|
||||
}
|
||||
else {
|
||||
@@ -111,7 +104,7 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
public final Object read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map<String, Object> hints)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
KSerializer<Object> serializer = serializer(type);
|
||||
KSerializer<Object> serializer = serializer(type, hints);
|
||||
if (serializer == null) {
|
||||
throw new HttpMessageNotReadableException("Could not find KSerializer for " + type, inputMessage);
|
||||
}
|
||||
@@ -129,7 +122,7 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
@Nullable Map<String, Object> hints) throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
ResolvableType resolvableType = (ResolvableType.NONE.equals(type) ? ResolvableType.forInstance(object) : type);
|
||||
KSerializer<Object> serializer = serializer(resolvableType);
|
||||
KSerializer<Object> serializer = serializer(resolvableType, hints);
|
||||
if (serializer == null) {
|
||||
throw new HttpMessageNotWritableException("Could not find KSerializer for " + resolvableType);
|
||||
}
|
||||
@@ -149,29 +142,21 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
* @param resolvableType the type to find a serializer for
|
||||
* @return a resolved serializer for the given type, or {@code null}
|
||||
*/
|
||||
private @Nullable KSerializer<Object> serializer(ResolvableType resolvableType) {
|
||||
if (resolvableType.getSource() instanceof MethodParameter parameter) {
|
||||
Method method = parameter.getMethod();
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
if (function != null) {
|
||||
KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() :
|
||||
KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType());
|
||||
KSerializer<Object> serializer = this.kTypeSerializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
try {
|
||||
serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type);
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
if (serializer != null) {
|
||||
this.kTypeSerializerCache.put(type, serializer);
|
||||
}
|
||||
}
|
||||
return serializer;
|
||||
private @Nullable KSerializer<Object> serializer(ResolvableType resolvableType, @Nullable Map<String, Object> hints) {
|
||||
if (hints != null && hints.containsKey(KType.class.getName())) {
|
||||
KType type = (KType) hints.get(KType.class.getName());
|
||||
KSerializer<Object> serializer = this.kTypeSerializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
try {
|
||||
serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type);
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
if (serializer != null) {
|
||||
this.kTypeSerializerCache.put(type, serializer);
|
||||
}
|
||||
}
|
||||
return serializer;
|
||||
}
|
||||
Type type = resolvableType.getType();
|
||||
KSerializer<Object> serializer = this.typeSerializerCache.get(type);
|
||||
|
||||
@@ -33,8 +33,10 @@ import org.springframework.web.testfixture.http.MockHttpOutputMessage
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.math.BigDecimal
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.javaType
|
||||
import kotlin.reflect.jvm.javaMethod
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
@@ -246,7 +248,10 @@ class KotlinSerializationJsonHttpMessageConverterTests {
|
||||
val inputMessage = MockHttpInputMessage(body.toByteArray(StandardCharsets.UTF_8))
|
||||
inputMessage.headers.contentType = MediaType.APPLICATION_JSON
|
||||
val methodParameter = MethodParameter.forExecutable(::handleMapWithNullable::javaMethod.get()!!, 0)
|
||||
val result = converter.read(ResolvableType.forMethodParameter(methodParameter), inputMessage, null) as Map<String, String?>
|
||||
val hints = mapOf(KType::class.jvmName to typeOf<Map<String, String?>>())
|
||||
|
||||
val result = converter.read(ResolvableType.forMethodParameter(methodParameter), inputMessage,
|
||||
hints) as Map<String, String?>
|
||||
|
||||
assertThat(result).containsExactlyEntriesOf(mapOf("value" to null))
|
||||
}
|
||||
@@ -400,9 +405,10 @@ class KotlinSerializationJsonHttpMessageConverterTests {
|
||||
val serializableBean = mapOf<String, String?>("value" to null)
|
||||
val expectedJson = """{"value":null}"""
|
||||
val methodParameter = MethodParameter.forExecutable(::handleMapWithNullable::javaMethod.get()!!, -1)
|
||||
val hints = mapOf(KType::class.jvmName to typeOf<Map<String, String?>>())
|
||||
|
||||
this.converter.write(serializableBean, ResolvableType.forMethodParameter(methodParameter), null,
|
||||
outputMessage, null)
|
||||
outputMessage, hints)
|
||||
|
||||
val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -106,8 +105,12 @@ import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionRes
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.KotlinRequestBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.KotlinResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
|
||||
@@ -225,6 +228,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
|
||||
private static final boolean jsonbPresent;
|
||||
|
||||
private static final boolean kotlinSerializationPresent;
|
||||
|
||||
private static final boolean kotlinSerializationCborPresent;
|
||||
|
||||
private static final boolean kotlinSerializationJsonPresent;
|
||||
@@ -248,9 +253,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
|
||||
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
|
||||
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
|
||||
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
|
||||
kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
|
||||
kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);
|
||||
kotlinSerializationPresent = ClassUtils.isPresent("kotlinx.serialization.Serializable", classLoader);
|
||||
kotlinSerializationCborPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
|
||||
kotlinSerializationJsonPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
|
||||
kotlinSerializationProtobufPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);
|
||||
}
|
||||
|
||||
|
||||
@@ -699,9 +705,19 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
|
||||
adapter.setErrorResponseInterceptors(getErrorResponseInterceptors());
|
||||
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
|
||||
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
|
||||
if (jacksonPresent || jackson2Present || kotlinSerializationPresent) {
|
||||
List<RequestBodyAdvice> requestBodyAdvices = new ArrayList<>(2);
|
||||
List<ResponseBodyAdvice<?>> responseBodyAdvices = new ArrayList<>(2);
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
requestBodyAdvices.add(new JsonViewRequestBodyAdvice());
|
||||
responseBodyAdvices.add(new JsonViewResponseBodyAdvice());
|
||||
}
|
||||
if (kotlinSerializationPresent) {
|
||||
requestBodyAdvices.add(new KotlinRequestBodyAdvice());
|
||||
responseBodyAdvices.add(new KotlinResponseBodyAdvice());
|
||||
}
|
||||
adapter.setRequestBodyAdvice(requestBodyAdvices);
|
||||
adapter.setResponseBodyAdvice(responseBodyAdvices);
|
||||
}
|
||||
|
||||
AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
|
||||
@@ -1122,9 +1138,15 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
|
||||
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
|
||||
exceptionHandlerResolver.setErrorResponseInterceptors(getErrorResponseInterceptors());
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
exceptionHandlerResolver.setResponseBodyAdvice(
|
||||
Collections.singletonList(new JsonViewResponseBodyAdvice()));
|
||||
if (jacksonPresent || jackson2Present || kotlinSerializationPresent) {
|
||||
List<ResponseBodyAdvice<?>> responseBodyAdvices = new ArrayList<>(2);
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
responseBodyAdvices.add(new JsonViewResponseBodyAdvice());
|
||||
}
|
||||
if (kotlinSerializationPresent) {
|
||||
responseBodyAdvices.add(new KotlinResponseBodyAdvice());
|
||||
}
|
||||
exceptionHandlerResolver.setResponseBodyAdvice(responseBodyAdvices);
|
||||
}
|
||||
if (this.applicationContext != null) {
|
||||
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.KType;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* A {@link RequestBodyAdvice} implementation that adds support for resolving
|
||||
* Kotlin {@link KType} from the parameter and providing it as a hint with a
|
||||
* {@code "kotlin.reflect.KType"} key.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
* @see AbstractKotlinSerializationHttpMessageConverter
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class KotlinRequestBodyAdvice extends RequestBodyAdviceAdapter {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter methodParameter, Type targetType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
|
||||
return AbstractKotlinSerializationHttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType,
|
||||
Class<? extends SmartHttpMessageConverter<?>> converterType) {
|
||||
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(parameter.getMethod()));
|
||||
int i = 0;
|
||||
int index = parameter.getParameterIndex();
|
||||
for (KParameter p : Objects.requireNonNull(function).getParameters()) {
|
||||
if (KParameter.Kind.VALUE.equals(p.getKind())) {
|
||||
if (index == i++) {
|
||||
return Collections.singletonMap(KType.class.getName(), p.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KType;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
/**
|
||||
* A {@link ResponseBodyAdvice} implementation that adds support for resolving
|
||||
* Kotlin {@link KType} from the return type and providing it as a hint with a
|
||||
* {@code "kotlin.reflect.KType"} key.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class KotlinResponseBodyAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return AbstractKotlinSerializationHttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Object> determineWriteHints(@Nullable Object body, MethodParameter returnType,
|
||||
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
|
||||
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(returnType.getMethod()));
|
||||
KType type = Objects.requireNonNull(function).getReturnType();
|
||||
return Collections.singletonMap(KType.class.getName(), type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,6 +80,8 @@ import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionRes
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.KotlinRequestBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.KotlinResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
@@ -204,9 +206,11 @@ class WebMvcConfigurationSupportTests {
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> bodyAdvice = (List<Object>) fieldAccessor.getPropertyValue("requestResponseBodyAdvice");
|
||||
assertThat(bodyAdvice).hasSize(2);
|
||||
assertThat(bodyAdvice).hasSize(4);
|
||||
assertThat(bodyAdvice.get(0).getClass()).isEqualTo(JsonViewRequestBodyAdvice.class);
|
||||
assertThat(bodyAdvice.get(1).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class);
|
||||
assertThat(bodyAdvice.get(1).getClass()).isEqualTo(KotlinRequestBodyAdvice.class);
|
||||
assertThat(bodyAdvice.get(2).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class);
|
||||
assertThat(bodyAdvice.get(3).getClass()).isEqualTo(KotlinResponseBodyAdvice.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -238,8 +242,9 @@ class WebMvcConfigurationSupportTests {
|
||||
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(eher);
|
||||
List<Object> interceptors = (List<Object>) fieldAccessor.getPropertyValue("responseBodyAdvice");
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors).hasSize(2);
|
||||
assertThat(interceptors.get(0).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class);
|
||||
assertThat(interceptors.get(1).getClass()).isEqualTo(KotlinResponseBodyAdvice.class);
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.ENGLISH);
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.web.method.support.ModelAndViewContainer
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.collections.mapOf
|
||||
import kotlin.reflect.jvm.javaMethod
|
||||
|
||||
/**
|
||||
@@ -84,6 +85,22 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||
.contains("\"value\":\"bar\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun writeNullableMapWithKotlinSerializationJsonMessageConverter() {
|
||||
val method = SampleController::writeNullableMap::javaMethod.get()!!
|
||||
val handlerMethod = HandlerMethod(SampleController(), method)
|
||||
val methodReturnType = handlerMethod.returnType
|
||||
|
||||
val converters = listOf(KotlinSerializationJsonHttpMessageConverter())
|
||||
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinResponseBodyAdvice()))
|
||||
|
||||
val returnValue: Any = SampleController().writeNullableMap()
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
|
||||
|
||||
Assertions.assertThat(this.servletResponse.contentAsString)
|
||||
.isEqualTo("""{"value":null}""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readWithKotlinSerializationJsonMessageConverter() {
|
||||
val content = "{\"value\" : \"foo\"}"
|
||||
@@ -119,6 +136,24 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||
Assertions.assertThat(result).containsExactly(Message("foo"), Message("bar"))
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Test
|
||||
fun readNullableMapWithKotlinSerializationJsonMessageConverter() {
|
||||
val content = "{\"value\" : null}"
|
||||
this.servletRequest.setContent(content.toByteArray(StandardCharsets.UTF_8))
|
||||
this.servletRequest.setContentType("application/json")
|
||||
|
||||
val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter())
|
||||
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinRequestBodyAdvice()))
|
||||
|
||||
val method = SampleController::readNullableMap::javaMethod.get()!!
|
||||
val methodParameter = MethodParameter(method, 0)
|
||||
|
||||
val result = processor.resolveArgument(methodParameter, container, request, factory) as Map<String, String ?>
|
||||
|
||||
Assertions.assertThat(result).isEqualTo(mapOf("value" to null))
|
||||
}
|
||||
|
||||
|
||||
private class SampleController {
|
||||
|
||||
@@ -135,7 +170,20 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||
fun readMessage(message: Message) = message.value
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }
|
||||
@ResponseBody
|
||||
fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody
|
||||
fun writeNullableMap(): Map<String, String?> {
|
||||
return mapOf("value" to null)
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody
|
||||
fun readNullableMap(map: Map<String, String?>): String {
|
||||
return map.toString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user