diff --git a/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java index 0c73ecc01a0..e5a17226f65 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -17,22 +17,30 @@ package org.springframework.http.converter; import java.io.IOException; +import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Map; import java.util.Set; +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 kotlinx.serialization.descriptors.PolymorphicKind; import kotlinx.serialization.descriptors.SerialDescriptor; -import org.springframework.core.GenericTypeResolver; +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.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -48,9 +56,11 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @since 6.0 * @param the type of {@link SerialFormat} */ -public abstract class AbstractKotlinSerializationHttpMessageConverter extends AbstractGenericHttpMessageConverter { +public abstract class AbstractKotlinSerializationHttpMessageConverter extends AbstractSmartHttpMessageConverter { - private final Map> serializerCache = new ConcurrentReferenceHashMap<>(); + private final Map> kTypeSerializerCache = new ConcurrentReferenceHashMap<>(); + + private final Map> typeSerializerCache = new ConcurrentReferenceHashMap<>(); private final T format; @@ -66,15 +76,14 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter clazz) { - return serializer(clazz) != null; + return serializer(ResolvableType.forClass(clazz)) != null; } @Override - public boolean canRead(Type type, @Nullable Class contextClass, @Nullable MediaType mediaType) { - if (serializer(GenericTypeResolver.resolveType(type, contextClass)) != null) { + public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) { + if (!ResolvableType.NONE.equals(type) && serializer(type) != null) { return canRead(mediaType); } else { @@ -83,8 +92,8 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter clazz, @Nullable MediaType mediaType) { - if (serializer(type != null ? GenericTypeResolver.resolveType(type, clazz) : clazz) != null) { + public boolean canWrite(ResolvableType type, Class clazz, @Nullable MediaType mediaType) { + if (!ResolvableType.NONE.equals(type) && serializer(type) != null) { return canWrite(mediaType); } else { @@ -93,24 +102,12 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter contextClass, HttpInputMessage inputMessage) + public final Object read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map hints) throws IOException, HttpMessageNotReadableException { - Type resolvedType = GenericTypeResolver.resolveType(type, contextClass); - KSerializer serializer = serializer(resolvedType); + KSerializer serializer = serializer(type); if (serializer == null) { - throw new HttpMessageNotReadableException("Could not find KSerializer for " + resolvedType, inputMessage); - } - return readInternal(serializer, this.format, inputMessage); - } - - @Override - protected final Object readInternal(Class clazz, HttpInputMessage inputMessage) - throws IOException, HttpMessageNotReadableException { - - KSerializer serializer = serializer(clazz); - if (serializer == null) { - throw new HttpMessageNotReadableException("Could not find KSerializer for " + clazz, inputMessage); + throw new HttpMessageNotReadableException("Could not find KSerializer for " + type, inputMessage); } return readInternal(serializer, this.format, inputMessage); } @@ -122,13 +119,13 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter hints) throws IOException, HttpMessageNotWritableException { - Type resolvedType = type != null ? type : object.getClass(); - KSerializer serializer = serializer(resolvedType); + ResolvableType resolvableType = (ResolvableType.NONE.equals(type) ? ResolvableType.forInstance(object) : type); + KSerializer serializer = serializer(resolvableType); if (serializer == null) { - throw new HttpMessageNotWritableException("Could not find KSerializer for " + resolvedType); + throw new HttpMessageNotWritableException("Could not find KSerializer for " + resolvableType); } writeInternal(object, serializer, this.format, outputMessage); } @@ -143,12 +140,38 @@ public abstract class AbstractKotlinSerializationHttpMessageConverterResolved serializers are cached and cached results are returned on successive calls. - * @param type the type to find a serializer for + * @param resolvableType the type to find a serializer for * @return a resolved serializer for the given type, or {@code null} */ @Nullable - private KSerializer serializer(Type type) { - KSerializer serializer = this.serializerCache.get(type); + private KSerializer 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); + Assert.notNull(function, "Kotlin function must not be null"); + KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() : + KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType()); + KSerializer serializer = this.kTypeSerializerCache.get(type); + if (serializer == null) { + try { + serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); + } + catch (IllegalArgumentException ignored) { + } + if (serializer != null) { + if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) { + return null; + } + this.kTypeSerializerCache.put(type, serializer); + } + } + return serializer; + } + } + Type type = resolvableType.getType(); + KSerializer serializer = this.typeSerializerCache.get(type); if (serializer == null) { try { serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); @@ -159,7 +182,7 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter())) { return null; } - this.serializerCache.put(type, serializer); + this.typeSerializerCache.put(type, serializer); } } return serializer; diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt index d98e2e2d72f..52e55ab22d5 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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. @@ -17,7 +17,6 @@ package org.springframework.http.converter.cbor import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type import java.nio.charset.StandardCharsets import kotlin.reflect.javaType @@ -31,6 +30,7 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.Ordered +import org.springframework.core.ResolvableType import org.springframework.http.MediaType import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.web.testfixture.http.MockHttpInputMessage @@ -67,18 +67,18 @@ class KotlinSerializationCborHttpMessageConverterTests { assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isFalse() + assertThat(converter.canRead(resolvableTypeOf(), MediaType.APPLICATION_CBOR)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_CBOR)).isFalse() } @Test @@ -89,17 +89,17 @@ class KotlinSerializationCborHttpMessageConverterTests { assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse() } @Test @@ -139,7 +139,7 @@ class KotlinSerializationCborHttpMessageConverterTests { fun readGenericCollection() { val inputMessage = MockHttpInputMessage(serializableBeanArrayBody) inputMessage.headers.contentType = MediaType.APPLICATION_CBOR - val result = converter.read(typeOf>().javaType, null, inputMessage) + val result = converter.read(ResolvableType.forType(typeOf>().javaType), inputMessage, null) as List assertThat(result).hasSize(1) @@ -200,7 +200,7 @@ class KotlinSerializationCborHttpMessageConverterTests { fun writeGenericCollection() { val outputMessage = MockHttpOutputMessage() - this.converter.write(listOf(serializableBean), typeOf>().javaType, null, outputMessage) + this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf>().javaType), null, outputMessage, null) assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() @@ -222,10 +222,10 @@ class KotlinSerializationCborHttpMessageConverterTests { open class TypeBase - inline fun typeTokenOf(): Type { + private inline fun resolvableTypeOf(): ResolvableType { val base = object : TypeBase() {} val superType = base::class.java.genericSuperclass!! - return (superType as ParameterizedType).actualTypeArguments.first()!! + return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!) } } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt index b4bd9d47112..e90047201fe 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt @@ -24,6 +24,7 @@ import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test +import org.springframework.core.MethodParameter import kotlin.reflect.javaType import kotlin.reflect.typeOf @@ -35,6 +36,7 @@ import org.springframework.http.customJson import org.springframework.web.testfixture.http.MockHttpInputMessage import org.springframework.web.testfixture.http.MockHttpOutputMessage import java.math.BigDecimal +import kotlin.reflect.jvm.javaMethod /** * Tests for the JSON conversion using kotlinx.serialization. @@ -55,22 +57,22 @@ class KotlinSerializationJsonHttpMessageConverterTests { assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_PDF)).isFalse() - assertThat(converter.canRead(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(resolvableTypeOf(), MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(ResolvableType.NONE.type, null, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(ResolvableType.forType(ResolvableType.NONE.type), MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(BigDecimal::class.java, null, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(ResolvableType.forType(BigDecimal::class.java), MediaType.APPLICATION_JSON)).isFalse() } @Test @@ -81,21 +83,21 @@ class KotlinSerializationJsonHttpMessageConverterTests { assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() - assertThat(converter.canWrite(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(ResolvableType.NONE.type, SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(ResolvableType.NONE, SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(BigDecimal::class.java, BigDecimal::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(ResolvableType.forType(BigDecimal::class.java), BigDecimal::class.java, MediaType.APPLICATION_JSON)).isFalse() } @Test @@ -198,7 +200,7 @@ class KotlinSerializationJsonHttpMessageConverterTests { """.trimIndent() val inputMessage = MockHttpInputMessage(body.toByteArray(charset("UTF-8"))) inputMessage.headers.contentType = MediaType.APPLICATION_JSON - val result = converter.read(typeOf>().javaType, null, inputMessage) + val result = converter.read(ResolvableType.forType(typeOf>().javaType), inputMessage, null) as List assertThat(result).hasSize(1) @@ -234,6 +236,19 @@ class KotlinSerializationJsonHttpMessageConverterTests { } } + @Test + @Suppress("UNCHECKED_CAST") + fun readNullableWithNull() { + val body = """{"value":null}""" + + 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 + + assertThat(result).containsExactlyEntriesOf(mapOf("value" to null)) + } + @Test fun writeObject() { val outputMessage = MockHttpOutputMessage() @@ -297,8 +312,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { [{"bytes":[1,2],"array":["Foo","Bar"],"number":42,"string":"Foo","bool":true,"fraction":42.0}] """.trimIndent() - this.converter.write(arrayListOf(serializableBean), typeOf>().javaType, null, - outputMessage) + this.converter.write(arrayListOf(serializableBean), ResolvableType.forType(typeOf>().javaType), null, + outputMessage, null) val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) @@ -356,6 +371,23 @@ class KotlinSerializationJsonHttpMessageConverterTests { assertThat(result).isEqualTo("1.0") } + @Test + @ExperimentalStdlibApi + fun writeNullableWithNull() { + val outputMessage = MockHttpOutputMessage() + val serializableBean = mapOf("value" to null) + val expectedJson = """{"value":null}""" + val methodParameter = MethodParameter.forExecutable(::handleMapWithNullable::javaMethod.get()!!, -1) + + this.converter.write(serializableBean, ResolvableType.forMethodParameter(methodParameter), null, + outputMessage, null) + + val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) + + assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + assertThat(result).isEqualTo(expectedJson) + } + @Serializable @Suppress("ArrayInDataClass") @@ -373,10 +405,12 @@ class KotlinSerializationJsonHttpMessageConverterTests { open class TypeBase - inline fun typeTokenOf(): Type { + private inline fun resolvableTypeOf(): ResolvableType { val base = object : TypeBase() {} val superType = base::class.java.genericSuperclass!! - return (superType as ParameterizedType).actualTypeArguments.first()!! + return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!) } + fun handleMapWithNullable(map: Map) = map + } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt index d74d530734e..b79efc9929e 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt @@ -24,12 +24,12 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.Ordered +import org.springframework.core.ResolvableType import org.springframework.http.MediaType import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.web.testfixture.http.MockHttpInputMessage import org.springframework.web.testfixture.http.MockHttpOutputMessage import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type import java.nio.charset.StandardCharsets import kotlin.reflect.javaType import kotlin.reflect.typeOf @@ -69,20 +69,20 @@ class KotlinSerializationProtobufHttpMessageConverterTests { assertThat(converter.canRead(NotSerializableBean::class.java, mimeType)).isFalse() assertThat(converter.canRead(Map::class.java, mimeType)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Map::class.java, mimeType)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), mimeType)).isTrue() assertThat(converter.canRead(List::class.java, mimeType)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, mimeType)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), mimeType)).isTrue() assertThat(converter.canRead(Set::class.java, mimeType)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), Set::class.java, mimeType)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), mimeType)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, mimeType)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, mimeType)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(), mimeType)).isTrue() + assertThat(converter.canRead(resolvableTypeOf>(),mimeType)).isTrue() - assertThat(converter.canRead(typeTokenOf(), Ordered::class.java, mimeType)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, mimeType)).isFalse() + assertThat(converter.canRead(resolvableTypeOf(), mimeType)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), mimeType)).isFalse() } assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(resolvableTypeOf>(), MediaType.APPLICATION_JSON)).isFalse() } @Test @@ -93,20 +93,20 @@ class KotlinSerializationProtobufHttpMessageConverterTests { assertThat(converter.canWrite(NotSerializableBean::class.java, mimeType)).isFalse() assertThat(converter.canWrite(Map::class.java, mimeType)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Map::class.java, mimeType)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Map::class.java, mimeType)).isTrue() assertThat(converter.canWrite(List::class.java, mimeType)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, mimeType)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, mimeType)).isTrue() assertThat(converter.canWrite(Set::class.java, mimeType)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), Set::class.java, mimeType)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), Set::class.java, mimeType)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, mimeType)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, mimeType)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, mimeType)).isTrue() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, mimeType)).isTrue() - assertThat(converter.canWrite(typeTokenOf(), Ordered::class.java, mimeType)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf(), Ordered::class.java, mimeType)).isFalse() } assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(resolvableTypeOf>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() } @Test @@ -152,8 +152,8 @@ class KotlinSerializationProtobufHttpMessageConverterTests { for (mimeType in mediaTypes) { val inputMessage = MockHttpInputMessage(serializableBeanArrayBody) inputMessage.headers.contentType = mimeType - val result = converter.read(typeOf>().javaType, null, inputMessage) - as List + val result = converter.read(ResolvableType.forType(typeOf>().javaType), inputMessage, + null) as List assertThat(result).hasSize(1) assertThat(result[0].bytes).containsExactly(*serializableBean.bytes) @@ -216,7 +216,7 @@ class KotlinSerializationProtobufHttpMessageConverterTests { fun writeGenericCollection() { val outputMessage = MockHttpOutputMessage() - this.converter.write(listOf(serializableBean), typeOf>().javaType, null, outputMessage) + this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf>().javaType), null, outputMessage, null) assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() @@ -238,10 +238,10 @@ class KotlinSerializationProtobufHttpMessageConverterTests { open class TypeBase - inline fun typeTokenOf(): Type { + private inline fun resolvableTypeOf(): ResolvableType { val base = object : TypeBase() {} val superType = base::class.java.genericSuperclass!! - return (superType as ParameterizedType).actualTypeArguments.first()!! + return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!) } }