From 83d0faa2a26b7c7504d3ce94834ca76a2b33c76c Mon Sep 17 00:00:00 2001 From: Matthijs van den Bos Date: Tue, 13 Apr 2021 20:31:20 +0200 Subject: [PATCH] Fix serializer lookup by KType for third party generics (#1397) This lookup used to fail, because `SerializersModule.serializerByKTypeImpl()` never tried `getContextual()` after constructSerializerForGivenTypeArgs() fails to find a serializer (and that *will* fail, because `ThirdPartyBox` is not annotated as @Serializable, since it is a third party class). This does work when doing the look up by Java type with `serializerByJavaTypeImpl.serializerByJavaTypeImpe()`. This commit adds the call to `reflectiveOrContextual()` to `SerializersModule.serializerByKTypeImpl()`. The accompanying test illustrates the scenario where this used to fail. --- .../src/kotlinx/serialization/Serializers.kt | 7 +++ .../features/ThirdPartyGenericsTest.kt | 45 +++++++++++++++++++ .../kotlinx/serialization/SerializersJvm.kt | 5 --- .../features/JvmThirdPartyGenericsTest.kt | 13 ++++++ 4 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt create mode 100644 core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index cc1b347aa9..76faf0f7cc 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -115,10 +115,17 @@ private fun SerializersModule.builtinSerializer( return ArraySerializer(typeArguments[0].classifier as KClass, serializers[0]).cast() } rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray()) + ?: reflectiveOrContextual(rootClass) } } } +@OptIn(ExperimentalSerializationApi::class) +internal fun SerializersModule.reflectiveOrContextual(kClass: KClass): KSerializer? { + return kClass.serializerOrNull() ?: getContextual(kClass) +} + + /** * Retrieves a [KSerializer] for the given [KClass]. * The given class must be annotated with [Serializable] or be one of the built-in types. diff --git a/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt b/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt new file mode 100644 index 0000000000..e8d3eed6d5 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt @@ -0,0 +1,45 @@ +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* +import kotlin.test.* + +open class ThirdPartyGenericsTest { + // This is a 3rd party class that we can't annotate as @Serializable + data class ThirdPartyBox(val contents: T) + + // This is the item that we put in the ThirdPartyBox, we control it, so can annotate it + @Serializable + data class Item(val name: String) + + // The serializer for the ThirdPartyBox + class BoxSerializer(dataSerializer: KSerializer) : KSerializer> { + @Serializable + data class BoxSurrogate(val contents: T) + + private val strategy = BoxSurrogate.serializer(dataSerializer) + override val descriptor: SerialDescriptor = strategy.descriptor + + override fun deserialize(decoder: Decoder): ThirdPartyBox { + return ThirdPartyBox(decoder.decodeSerializableValue(strategy).contents) + } + + override fun serialize(encoder: Encoder, value: ThirdPartyBox) { + encoder.encodeSerializableValue(strategy, BoxSurrogate(value.contents)) + } + } + + // Register contextual serializer for ThirdPartyBox + protected val boxWithItemSerializer = BoxSerializer(Item.serializer()) + protected val serializersModule = SerializersModule { + contextual(boxWithItemSerializer) + } + + @Test + fun testSurrogateSerializerFoundForGenericWithKotlinType() { + val serializer = serializersModule.serializer>() + assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor) + } +} \ No newline at end of file diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt index 34943229c1..a89b531c87 100644 --- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt +++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt @@ -154,11 +154,6 @@ private fun SerializersModule.genericArraySerializer( return ArraySerializer(kclass, serializer) as KSerializer } -@OptIn(ExperimentalSerializationApi::class) -private fun SerializersModule.reflectiveOrContextual(kClass: KClass): KSerializer? { - return kClass.serializerOrNull() ?: getContextual(kClass) -} - private fun Type.kclass(): KClass<*> = when (val it = this) { is KClass<*> -> it is Class<*> -> it.kotlin diff --git a/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt b/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt new file mode 100644 index 0000000000..0a43047aee --- /dev/null +++ b/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt @@ -0,0 +1,13 @@ +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlin.test.* + +class JvmThirdPartyGenericsTest : ThirdPartyGenericsTest() { + @Test + fun testSurrogateSerializerFoundForGenericWithJavaType() { + val filledBox = ThirdPartyBox(contents = Item("Foo")) + val serializer = serializersModule.serializer(filledBox::class.java) + assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor) + } +} \ No newline at end of file