Leverage KType in Kotlin Serialization WebMVC support
In order to take in account properly Kotlin null-safety with the annotation programming model. See gh-33016
This commit is contained in:
@@ -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 <T> the type of {@link SerialFormat}
|
||||
*/
|
||||
public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends SerialFormat> extends AbstractGenericHttpMessageConverter<Object> {
|
||||
public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends SerialFormat> extends AbstractSmartHttpMessageConverter<Object> {
|
||||
|
||||
private final Map<Type, KSerializer<Object>> serializerCache = new ConcurrentReferenceHashMap<>();
|
||||
private final Map<KType, KSerializer<Object>> kTypeSerializerCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
private final Map<Type, KSerializer<Object>> typeSerializerCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
private final T format;
|
||||
|
||||
@@ -66,15 +76,14 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> 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<T extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(@Nullable Type type, Class<?> 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<T extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
|
||||
public final Object read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map<String, Object> hints)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
Type resolvedType = GenericTypeResolver.resolveType(type, contextClass);
|
||||
KSerializer<Object> serializer = serializer(resolvedType);
|
||||
KSerializer<Object> 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<Object> 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<T extends
|
||||
throws IOException, HttpMessageNotReadableException;
|
||||
|
||||
@Override
|
||||
protected final void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
protected final void writeInternal(Object object, ResolvableType type, HttpOutputMessage outputMessage,
|
||||
@Nullable Map<String, Object> hints) throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
Type resolvedType = type != null ? type : object.getClass();
|
||||
KSerializer<Object> serializer = serializer(resolvedType);
|
||||
ResolvableType resolvableType = (ResolvableType.NONE.equals(type) ? ResolvableType.forInstance(object) : type);
|
||||
KSerializer<Object> 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 AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
* Tries to find a serializer that can marshall or unmarshall instances of the given type
|
||||
* using kotlinx.serialization. If no serializer can be found, {@code null} is returned.
|
||||
* <p>Resolved 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<Object> serializer(Type type) {
|
||||
KSerializer<Object> serializer = this.serializerCache.get(type);
|
||||
private 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);
|
||||
Assert.notNull(function, "Kotlin function must not be 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) {
|
||||
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
|
||||
return null;
|
||||
}
|
||||
this.kTypeSerializerCache.put(type, serializer);
|
||||
}
|
||||
}
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
Type type = resolvableType.getType();
|
||||
KSerializer<Object> serializer = this.typeSerializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
try {
|
||||
serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type);
|
||||
@@ -159,7 +182,7 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
|
||||
return null;
|
||||
}
|
||||
this.serializerCache.put(type, serializer);
|
||||
this.typeSerializerCache.put(type, serializer);
|
||||
}
|
||||
}
|
||||
return serializer;
|
||||
|
||||
@@ -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<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isFalse()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Ordered>>(), List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), 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<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), 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<List<SerializableBean>>().javaType, null, inputMessage)
|
||||
val result = converter.read(ResolvableType.forType(typeOf<List<SerializableBean>>().javaType), inputMessage, null)
|
||||
as List<SerializableBean>
|
||||
|
||||
assertThat(result).hasSize(1)
|
||||
@@ -200,7 +200,7 @@ class KotlinSerializationCborHttpMessageConverterTests {
|
||||
fun writeGenericCollection() {
|
||||
val outputMessage = MockHttpOutputMessage()
|
||||
|
||||
this.converter.write(listOf(serializableBean), typeOf<List<SerializableBean>>().javaType, null, outputMessage)
|
||||
this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf<List<SerializableBean>>().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<T>
|
||||
|
||||
inline fun <reified T> typeTokenOf(): Type {
|
||||
private inline fun <reified T> resolvableTypeOf(): ResolvableType {
|
||||
val base = object : TypeBase<T>() {}
|
||||
val superType = base::class.java.genericSuperclass!!
|
||||
return (superType as ParameterizedType).actualTypeArguments.first()!!
|
||||
return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_PDF)).isFalse()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Ordered>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), 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<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), 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<List<SerializableBean>>().javaType, null, inputMessage)
|
||||
val result = converter.read(ResolvableType.forType(typeOf<List<SerializableBean>>().javaType), inputMessage, null)
|
||||
as List<SerializableBean>
|
||||
|
||||
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<String, String?>
|
||||
|
||||
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<List<SerializableBean>>().javaType, null,
|
||||
outputMessage)
|
||||
this.converter.write(arrayListOf(serializableBean), ResolvableType.forType(typeOf<List<SerializableBean>>().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<String, String?>("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<T>
|
||||
|
||||
inline fun <reified T> typeTokenOf(): Type {
|
||||
private inline fun <reified T> resolvableTypeOf(): ResolvableType {
|
||||
val base = object : TypeBase<T>() {}
|
||||
val superType = base::class.java.genericSuperclass!!
|
||||
return (superType as ParameterizedType).actualTypeArguments.first()!!
|
||||
return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!)
|
||||
}
|
||||
|
||||
fun handleMapWithNullable(map: Map<String, String?>) = map
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), mimeType)).isTrue()
|
||||
assertThat(converter.canRead(List::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), mimeType)).isTrue()
|
||||
assertThat(converter.canRead(Set::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), mimeType)).isTrue()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canRead(typeTokenOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), mimeType)).isTrue()
|
||||
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(),mimeType)).isTrue()
|
||||
|
||||
assertThat(converter.canRead(typeTokenOf<Ordered>(), Ordered::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Ordered>>(), List::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), mimeType)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), mimeType)).isFalse()
|
||||
}
|
||||
assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), 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<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(List::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(Set::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(typeTokenOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
|
||||
|
||||
assertThat(converter.canWrite(typeTokenOf<Ordered>(), Ordered::class.java, mimeType)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, mimeType)).isFalse()
|
||||
}
|
||||
|
||||
assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
|
||||
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), 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<List<SerializableBean>>().javaType, null, inputMessage)
|
||||
as List<SerializableBean>
|
||||
val result = converter.read(ResolvableType.forType(typeOf<List<SerializableBean>>().javaType), inputMessage,
|
||||
null) as List<SerializableBean>
|
||||
|
||||
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<List<SerializableBean>>().javaType, null, outputMessage)
|
||||
this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf<List<SerializableBean>>().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<T>
|
||||
|
||||
inline fun <reified T> typeTokenOf(): Type {
|
||||
private inline fun <reified T> resolvableTypeOf(): ResolvableType {
|
||||
val base = object : TypeBase<T>() {}
|
||||
val superType = base::class.java.genericSuperclass!!
|
||||
return (superType as ParameterizedType).actualTypeArguments.first()!!
|
||||
return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user