Leverage KType in Kotlin Serialization WebFlux support

In order to take in account properly Kotlin null-safety with the
annotation programming model.

Closes gh-33016
This commit is contained in:
Sébastien Deleuze
2024-07-01 14:55:25 +02:00
parent 23dccc5977
commit 98e89d8fba
3 changed files with 75 additions and 3 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.http.codec;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
@@ -23,14 +24,21 @@ import java.util.List;
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.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.MimeType;
@@ -46,7 +54,10 @@ import org.springframework.util.MimeType;
*/
public abstract class KotlinSerializationSupport<T extends SerialFormat> {
private final Map<Type, KSerializer<Object>> serializerCache = new ConcurrentReferenceHashMap<>();
private final Map<Type, KSerializer<Object>> typeSerializerCache = new ConcurrentReferenceHashMap<>();
private final Map<KType, KSerializer<Object>> kTypeSerializerCache = new ConcurrentReferenceHashMap<>();
private final T format;
@@ -117,8 +128,33 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
*/
@Nullable
protected final 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.serializerCache.get(type);
KSerializer<Object> serializer = this.typeSerializerCache.get(type);
if (serializer == null) {
try {
serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type);
@@ -129,7 +165,7 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
return null;
}
this.serializerCache.put(type, serializer);
this.typeSerializerCache.put(type, serializer);
}
}
return serializer;