From c32d0a44080b4266d9ec7e891d98c4efddda261f Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 12 Apr 2021 21:39:13 +0300 Subject: [PATCH 1/7] Draft for contextual {} provider --- .../serialization/ContextualSerializer.kt | 4 +- .../modules/SerializersModule.kt | 75 ++++++++++++++++--- .../modules/SerializersModuleBuilders.kt | 28 ++++--- .../modules/SerializersModuleCollector.kt | 9 ++- .../modules/ContextualProviderTest.kt | 54 +++++++++++++ .../json/internal/PolymorphismValidator.kt | 5 +- 6 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt diff --git a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt index e43e62508..ee75c36f1 100644 --- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt @@ -42,11 +42,11 @@ import kotlin.reflect.* public class ContextualSerializer( private val serializableClass: KClass, private val fallbackSerializer: KSerializer?, - private val typeParametersSerializers: Array> + private val typeArgumentsSerializers: Array> ) : KSerializer { private fun serializer(serializersModule: SerializersModule): KSerializer = - serializersModule.getContextual(serializableClass) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() + serializersModule.getContextual(serializableClass, typeArgumentsSerializers) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() // Used from auto-generated code public constructor(serializableClass: KClass) : this(serializableClass, null, EMPTY_SERIALIZER_ARRAY) diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 9fbcafa79..85f614096 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -6,6 +6,7 @@ package kotlinx.serialization.modules import kotlinx.serialization.* import kotlinx.serialization.internal.* +import kotlin.internal.* import kotlin.jvm.* import kotlin.native.concurrent.* import kotlin.reflect.* @@ -28,7 +29,21 @@ public sealed class SerializersModule { * This method is used in context-sensitive operations on a property marked with [Contextual] by a [ContextualSerializer] */ @ExperimentalSerializationApi - public abstract fun getContextual(kclass: KClass): KSerializer? + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + @Deprecated( + "Deprecated in favor of overload with default parameter", + ReplaceWith("getContextual(kclass)"), + DeprecationLevel.ERROR + ) + @LowPriorityInOverloadResolution + public fun getContextual(kclass: KClass): KSerializer? = + getContextual(kclass, EMPTY_SERIALIZER_ARRAY) + + @ExperimentalSerializationApi + public abstract fun getContextual( + kClass: KClass, + typeArgumentsSerializers: Array> = EMPTY_SERIALIZER_ARRAY + ): KSerializer? /** * Returns a polymorphic serializer registered for a class of the given [value] in the scope of [baseClass]. @@ -74,11 +89,19 @@ public operator fun SerializersModule.plus(other: SerializersModule): Serializer * If serializer for some class presents in both modules, result module * will contain serializer from [other] module. */ +@OptIn(ExperimentalSerializationApi::class) public infix fun SerializersModule.overwriteWith(other: SerializersModule): SerializersModule = SerializersModule { include(this@overwriteWith) other.dumpTo(object : SerializersModuleCollector { override fun contextual(kClass: KClass, serializer: KSerializer) { - registerSerializer(kClass, serializer, allowOverwrite = true) + registerSerializer(kClass, ContextualProvider.ArglessProvider(serializer), allowOverwrite = true) + } + + override fun contextual( + kClass: KClass, + provider: (serializers: Array>) -> KSerializer<*> + ) { + registerSerializer(kClass, ContextualProvider.WithTypeArgumentsProvider(provider), allowOverwrite = true) } override fun polymorphic( @@ -105,8 +128,9 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri * which uses hash maps to store serializers associated with KClasses. */ @Suppress("UNCHECKED_CAST") +@OptIn(ExperimentalSerializationApi::class) internal class SerialModuleImpl( - private val class2Serializer: Map, KSerializer<*>>, + private val class2ContextualFactory: Map, ContextualProvider>, @JvmField val polyBase2Serializers: Map, Map, KSerializer<*>>>, private val polyBase2NamedSerializers: Map, Map>>, private val polyBase2DefaultProvider: Map, PolymorphicProvider<*>> @@ -125,15 +149,19 @@ internal class SerialModuleImpl( return (polyBase2DefaultProvider[baseClass] as? PolymorphicProvider)?.invoke(serializedClassName) } - override fun getContextual(kclass: KClass): KSerializer? = - class2Serializer[kclass] as? KSerializer + override fun getContextual(kClass: KClass, typeArgumentsSerializers: Array>): KSerializer? { + return (class2ContextualFactory[kClass]?.invoke(typeArgumentsSerializers)) as? KSerializer? + } override fun dumpTo(collector: SerializersModuleCollector) { - class2Serializer.forEach { (kclass, serial) -> - collector.contextual( - kclass as KClass, - serial.cast() - ) + class2ContextualFactory.forEach { (kclass, serial) -> + when (serial) { + is ContextualProvider.ArglessProvider -> collector.contextual( + kclass as KClass, + serial.serializer as KSerializer + ) + is ContextualProvider.WithTypeArgumentsProvider -> collector.contextual(kclass, serial.provider) + } } polyBase2Serializers.forEach { (baseClass, classMap) -> @@ -153,3 +181,30 @@ internal class SerialModuleImpl( } internal typealias PolymorphicProvider = (className: String?) -> DeserializationStrategy? + +/** This class is needed to support re-registering the same static (argless) serializers: + * + * ``` + * val m1 = serializersModuleOf(A::class, A.serializer()) + * val m2 = serializersModuleOf(A::class, A.serializer()) + * val aggregate = m1 + m2 // should not throw + * ``` + */ +internal sealed class ContextualProvider { + abstract operator fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> + + class ArglessProvider(val serializer: KSerializer<*>) : ContextualProvider() { + override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = serializer + + override fun equals(other: Any?): Boolean = other is ArglessProvider && other.serializer == this.serializer + + override fun hashCode(): Int = serializer.hashCode() + } + + class WithTypeArgumentsProvider(val provider: (typeArgumentsSerializers: Array>) -> KSerializer<*>) : + ContextualProvider() { + override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = + provider(typeArgumentsSerializers) + } + +} diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 11ffc1c11..3316976e8 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -4,6 +4,8 @@ package kotlinx.serialization.modules import kotlinx.serialization.* +import kotlinx.serialization.internal.* +import kotlinx.serialization.internal.EMPTY_SERIALIZER_ARRAY import kotlin.jvm.* import kotlin.reflect.* @@ -37,7 +39,7 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() - */ @OptIn(ExperimentalSerializationApi::class) public class SerializersModuleBuilder @PublishedApi internal constructor() : SerializersModuleCollector { - private val class2Serializer: MutableMap, KSerializer<*>> = hashMapOf() + private val class2ContextualProvider: MutableMap, ContextualProvider> = hashMapOf() private val polyBase2Serializers: MutableMap, MutableMap, KSerializer<*>>> = hashMapOf() private val polyBase2NamedSerializers: MutableMap, MutableMap>> = hashMapOf() private val polyBase2DefaultProvider: MutableMap, PolymorphicProvider<*>> = hashMapOf() @@ -48,7 +50,12 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser * To overwrite an already registered serializer, [SerializersModule.overwriteWith] can be used. */ public override fun contextual(kClass: KClass, serializer: KSerializer): Unit = - registerSerializer(kClass, serializer) + registerSerializer(kClass, ContextualProvider.ArglessProvider(serializer)) + + public override fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + ): Unit = registerSerializer(kClass, ContextualProvider.WithTypeArgumentsProvider(provider)) /** * Adds [serializer][actualSerializer] associated with given [actualClass] in the scope of [baseClass] for polymorphic serialization. @@ -88,22 +95,19 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser @JvmName("registerSerializer") // Don't mangle method name for prettier stack traces internal fun registerSerializer( forClass: KClass, - serializer: KSerializer, + provider: ContextualProvider, allowOverwrite: Boolean = false ) { if (!allowOverwrite) { - val previous = class2Serializer[forClass] - if (previous != null && previous != serializer) { - // TODO when working on SD rework, provide a way to properly stringify serializer as its FQN - val currentName = serializer.descriptor.serialName - val previousName = previous.descriptor.serialName + val previous = class2ContextualProvider[forClass] + if (previous != null && previous != provider) { + // How can we provide meaningful name for WithTypeArgumentsProvider ? throw SerializerAlreadyRegisteredException( - "Serializer for $forClass already registered in this module: $previous ($previousName), " + - "attempted to register $serializer ($currentName)" + "Contextual serializer or serializer provider for $forClass already registered in this module" ) } } - class2Serializer[forClass] = serializer + class2ContextualProvider[forClass] = provider } @JvmName("registerDefaultPolymorphicSerializer") // Don't mangle method name for prettier stack traces @@ -165,7 +169,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser @PublishedApi internal fun build(): SerializersModule = - SerialModuleImpl(class2Serializer, polyBase2Serializers, polyBase2NamedSerializers, polyBase2DefaultProvider) + SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2NamedSerializers, polyBase2DefaultProvider) } /** diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index 741881fa2..a68f57379 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -24,7 +24,14 @@ public interface SerializersModuleCollector { /** * Accept a serializer, associated with [kClass] for contextual serialization. */ - public fun contextual(kClass: KClass, serializer: KSerializer) + public fun contextual(kClass: KClass, serializer: KSerializer): Unit = + contextual(kClass) { _ -> serializer } + + + public fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + ) /** * Accept a serializer, associated with [actualClass] for polymorphic serialization. diff --git a/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt b/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt new file mode 100644 index 000000000..b22530d07 --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.modules + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.test.* + +class ContextualProviderTest { + // 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) + + // This is the another item that we put in the ThirdPartyBox, we control it, so can annotate it + @Serializable + data class AnotherItem(val value: Int) + + // The serializer for the ThirdPartyBox + class ThirdPartyBoxSerializer(itemSerializer: KSerializer) : KSerializer> { + @Serializable + data class BoxSurrogate(val contents: T) + + private val strategy = BoxSurrogate.serializer(itemSerializer) + 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)) + } + } + + @Test + fun testRegisterMultipleGenericSerializers() { + val kclass = ThirdPartyBox::class + val module = SerializersModule { + contextual(kclass) { args -> ThirdPartyBoxSerializer(args[0]) } + } + fun checkFor(serial: KSerializer<*>) { + val ser = module.getContextual(kclass, arrayOf(serial))?.descriptor + assertEquals(serial.descriptor, ser?.getElementDescriptor(0)) + } + checkFor(Item.serializer()) + checkFor(AnotherItem.serializer()) + } +} diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt index 231e7b33d..138310b17 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt @@ -15,7 +15,10 @@ internal class PolymorphismValidator( private val discriminator: String ) : SerializersModuleCollector { - override fun contextual(kClass: KClass, serializer: KSerializer) { + override fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + ) { // Nothing here } From d58fe0206b08045898b37924e0657273c6714f2a Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Wed, 14 Apr 2021 21:02:51 +0300 Subject: [PATCH 2/7] ~ support contextual providers in serializer() --- core/api/kotlinx-serialization-core.api | 10 ++- .../src/kotlinx/serialization/Serializers.kt | 11 +-- .../modules/SerializersModule.kt | 16 ++-- .../modules/SerializersModuleBuilders.kt | 7 +- .../features/ThirdPartyGenericsTest.kt | 45 ----------- .../modules/ContextualGenericsTest.kt | 74 +++++++++++++++++++ .../modules/ContextualProviderTest.kt | 54 -------------- .../kotlinx/serialization/SerializersJvm.kt | 6 +- .../features/JvmContextualGenericsTest.kt | 27 +++++++ .../features/JvmThirdPartyGenericsTest.kt | 13 ---- .../kotlinx/serialization/test/TypeToken.kt | 19 +++++ .../features/GenericCustomSerializerTest.kt | 22 +++++- 12 files changed, 169 insertions(+), 135 deletions(-) delete mode 100644 core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt create mode 100644 core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt delete mode 100644 core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt create mode 100644 core/jvmTest/src/kotlinx/serialization/features/JvmContextualGenericsTest.kt delete mode 100644 core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt create mode 100644 core/jvmTest/src/kotlinx/serialization/test/TypeToken.kt diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 4193f5b4a..ab010191f 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -1156,7 +1156,9 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder { public abstract class kotlinx/serialization/modules/SerializersModule { public abstract fun dumpTo (Lkotlinx/serialization/modules/SerializersModuleCollector;)V - public abstract fun getContextual (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; + public final fun getContextual (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; + public abstract fun getContextual (Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; + public static synthetic fun getContextual$default (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;ILjava/lang/Object;)Lkotlinx/serialization/KSerializer; public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy; public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy; } @@ -1164,6 +1166,7 @@ public abstract class kotlinx/serialization/modules/SerializersModule { public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotlinx/serialization/modules/SerializersModuleCollector { public fun ()V public final fun build ()Lkotlinx/serialization/modules/SerializersModule; + public fun contextual (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V public final fun include (Lkotlinx/serialization/modules/SerializersModule;)V public fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V @@ -1178,11 +1181,16 @@ public final class kotlinx/serialization/modules/SerializersModuleBuildersKt { } public abstract interface class kotlinx/serialization/modules/SerializersModuleCollector { + public abstract fun contextual (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public abstract fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V public abstract fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V public abstract fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } +public final class kotlinx/serialization/modules/SerializersModuleCollector$DefaultImpls { + public static fun contextual (Lkotlinx/serialization/modules/SerializersModuleCollector;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V +} + public final class kotlinx/serialization/modules/SerializersModuleKt { public static final fun getEmptySerializersModule ()Lkotlinx/serialization/modules/SerializersModule; public static final fun overwriteWith (Lkotlinx/serialization/modules/SerializersModule;Lkotlinx/serialization/modules/SerializersModule;)Lkotlinx/serialization/modules/SerializersModule; diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index 76faf0f7c..9b14d84b2 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @file:Suppress("DEPRECATION_ERROR", "UNCHECKED_CAST") @file:JvmMultifileClass @@ -114,15 +114,16 @@ private fun SerializersModule.builtinSerializer( if (isReferenceArray(rootClass)) { return ArraySerializer(typeArguments[0].classifier as KClass, serializers[0]).cast() } - rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray()) - ?: reflectiveOrContextual(rootClass) + val args = serializers.toTypedArray() + rootClass.constructSerializerForGivenTypeArgs(*args) + ?: reflectiveOrContextual(rootClass, args) } } } @OptIn(ExperimentalSerializationApi::class) -internal fun SerializersModule.reflectiveOrContextual(kClass: KClass): KSerializer? { - return kClass.serializerOrNull() ?: getContextual(kClass) +internal fun SerializersModule.reflectiveOrContextual(kClass: KClass, typeParameterSerializers: Array>): KSerializer? { + return kClass.serializerOrNull() ?: getContextual(kClass, typeParameterSerializers as Array>) } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 85f614096..e021a2d7a 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.modules @@ -94,14 +94,14 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri include(this@overwriteWith) other.dumpTo(object : SerializersModuleCollector { override fun contextual(kClass: KClass, serializer: KSerializer) { - registerSerializer(kClass, ContextualProvider.ArglessProvider(serializer), allowOverwrite = true) + registerSerializer(kClass, ContextualProvider.Argless(serializer), allowOverwrite = true) } override fun contextual( kClass: KClass, provider: (serializers: Array>) -> KSerializer<*> ) { - registerSerializer(kClass, ContextualProvider.WithTypeArgumentsProvider(provider), allowOverwrite = true) + registerSerializer(kClass, ContextualProvider.WithTypeArguments(provider), allowOverwrite = true) } override fun polymorphic( @@ -156,11 +156,11 @@ internal class SerialModuleImpl( override fun dumpTo(collector: SerializersModuleCollector) { class2ContextualFactory.forEach { (kclass, serial) -> when (serial) { - is ContextualProvider.ArglessProvider -> collector.contextual( + is ContextualProvider.Argless -> collector.contextual( kclass as KClass, serial.serializer as KSerializer ) - is ContextualProvider.WithTypeArgumentsProvider -> collector.contextual(kclass, serial.provider) + is ContextualProvider.WithTypeArguments -> collector.contextual(kclass, serial.provider) } } @@ -193,15 +193,15 @@ internal typealias PolymorphicProvider = (className: String?) -> Deseriali internal sealed class ContextualProvider { abstract operator fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> - class ArglessProvider(val serializer: KSerializer<*>) : ContextualProvider() { + class Argless(val serializer: KSerializer<*>) : ContextualProvider() { override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = serializer - override fun equals(other: Any?): Boolean = other is ArglessProvider && other.serializer == this.serializer + override fun equals(other: Any?): Boolean = other is Argless && other.serializer == this.serializer override fun hashCode(): Int = serializer.hashCode() } - class WithTypeArgumentsProvider(val provider: (typeArgumentsSerializers: Array>) -> KSerializer<*>) : + class WithTypeArguments(val provider: (typeArgumentsSerializers: Array>) -> KSerializer<*>) : ContextualProvider() { override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = provider(typeArgumentsSerializers) diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 3316976e8..b2625648a 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -1,11 +1,10 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.modules import kotlinx.serialization.* import kotlinx.serialization.internal.* -import kotlinx.serialization.internal.EMPTY_SERIALIZER_ARRAY import kotlin.jvm.* import kotlin.reflect.* @@ -50,12 +49,12 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser * To overwrite an already registered serializer, [SerializersModule.overwriteWith] can be used. */ public override fun contextual(kClass: KClass, serializer: KSerializer): Unit = - registerSerializer(kClass, ContextualProvider.ArglessProvider(serializer)) + registerSerializer(kClass, ContextualProvider.Argless(serializer)) public override fun contextual( kClass: KClass, provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> - ): Unit = registerSerializer(kClass, ContextualProvider.WithTypeArgumentsProvider(provider)) + ): Unit = registerSerializer(kClass, ContextualProvider.WithTypeArguments(provider)) /** * Adds [serializer][actualSerializer] associated with given [actualClass] in the scope of [baseClass] for polymorphic serialization. diff --git a/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt b/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt deleted file mode 100644 index e8d3eed6d..000000000 --- a/core/commonTest/src/kotlinx/serialization/features/ThirdPartyGenericsTest.kt +++ /dev/null @@ -1,45 +0,0 @@ -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/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt b/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt new file mode 100644 index 000000000..e08a2903c --- /dev/null +++ b/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.modules + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.test.* + +open class ContextualGenericsTest { + // 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) + + // This is the another item that we put in the ThirdPartyBox, we control it, so can annotate it + @Serializable + data class AnotherItem(val value: Int) + + // The serializer for the ThirdPartyBox + class ThirdPartyBoxSerializer(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 = ThirdPartyBoxSerializer(Item.serializer()) + protected val serializersModuleStatic = SerializersModule { + contextual(boxWithItemSerializer) + } + + protected val serializersModuleWithProvider = SerializersModule { + contextual(ThirdPartyBox::class) { args -> ThirdPartyBoxSerializer(args[0]) } + } + + @Test + fun testSurrogateSerializerFoundForGenericWithKotlinType() { + val serializer = serializersModuleStatic.serializer>() + assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor) + } + + @Test + fun testSerializerFoundForContextualGeneric() { + val serializerA = serializersModuleWithProvider.serializer>() + assertEquals(Item.serializer().descriptor, serializerA.descriptor.getElementDescriptor(0)) + val serializerB = serializersModuleWithProvider.serializer>() + assertEquals(AnotherItem.serializer().descriptor, serializerB.descriptor.getElementDescriptor(0)) + } + + @Test + fun testModuleProvidesMultipleGenericSerializers() { + fun checkFor(serial: KSerializer<*>) { + val serializer = serializersModuleWithProvider.getContextual(ThirdPartyBox::class, arrayOf(serial))?.descriptor + assertEquals(serial.descriptor, serializer?.getElementDescriptor(0)) + } + checkFor(Item.serializer()) + checkFor(AnotherItem.serializer()) + } +} diff --git a/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt b/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt deleted file mode 100644 index b22530d07..000000000 --- a/core/commonTest/src/kotlinx/serialization/modules/ContextualProviderTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.serialization.modules - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlin.test.* - -class ContextualProviderTest { - // 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) - - // This is the another item that we put in the ThirdPartyBox, we control it, so can annotate it - @Serializable - data class AnotherItem(val value: Int) - - // The serializer for the ThirdPartyBox - class ThirdPartyBoxSerializer(itemSerializer: KSerializer) : KSerializer> { - @Serializable - data class BoxSurrogate(val contents: T) - - private val strategy = BoxSurrogate.serializer(itemSerializer) - 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)) - } - } - - @Test - fun testRegisterMultipleGenericSerializers() { - val kclass = ThirdPartyBox::class - val module = SerializersModule { - contextual(kclass) { args -> ThirdPartyBoxSerializer(args[0]) } - } - fun checkFor(serial: KSerializer<*>) { - val ser = module.getContextual(kclass, arrayOf(serial))?.descriptor - assertEquals(serial.descriptor, ser?.getElementDescriptor(0)) - } - checkFor(Item.serializer()) - checkFor(AnotherItem.serializer()) - } -} diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt index a89b531c8..7fc4add7f 100644 --- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt +++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @file:JvmMultifileClass @file:JvmName("SerializersKt") @@ -114,7 +114,7 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing // since it uses Java TypeToken, not Kotlin one val varargs = argsSerializers.map { it as KSerializer }.toTypedArray() (rootClass.kotlin.constructSerializerForGivenTypeArgs(*varargs) as? KSerializer) - ?: reflectiveOrContextual(rootClass.kotlin as KClass) + ?: reflectiveOrContextual(rootClass.kotlin as KClass, varargs) } } } @@ -125,7 +125,7 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing @OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeArgSerializer: Boolean): KSerializer? { return if (!type.isArray) { - reflectiveOrContextual(type.kotlin as KClass) + reflectiveOrContextual(type.kotlin as KClass, emptyArray()) } else { val eType: Class<*> = type.componentType val s = if (failOnMissingTypeArgSerializer) serializer(eType) else (serializerOrNull(eType) ?: return null) diff --git a/core/jvmTest/src/kotlinx/serialization/features/JvmContextualGenericsTest.kt b/core/jvmTest/src/kotlinx/serialization/features/JvmContextualGenericsTest.kt new file mode 100644 index 000000000..7f0c54ca8 --- /dev/null +++ b/core/jvmTest/src/kotlinx/serialization/features/JvmContextualGenericsTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.modules.* +import kotlinx.serialization.test.* +import kotlin.test.* + +class JvmContextualGenericsTest : ContextualGenericsTest() { + @Test + fun testSurrogateSerializerFoundForGenericWithJavaType() { + val filledBox = ThirdPartyBox(contents = Item("Foo")) + val serializer = serializersModuleStatic.serializer(filledBox::class.java) + assertEquals(boxWithItemSerializer.descriptor, serializer.descriptor) + } + + @Test + fun testSerializerFoundForContextualGenericWithJavaTypeToken() { + val serializerA = serializersModuleWithProvider.serializer(typeTokenOf>()) + assertEquals(Item.serializer().descriptor, serializerA.descriptor.getElementDescriptor(0)) + val serializerB = serializersModuleWithProvider.serializer(typeTokenOf>()) + assertEquals(AnotherItem.serializer().descriptor, serializerB.descriptor.getElementDescriptor(0)) + } +} diff --git a/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt b/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt deleted file mode 100644 index 0a43047ae..000000000 --- a/core/jvmTest/src/kotlinx/serialization/features/JvmThirdPartyGenericsTest.kt +++ /dev/null @@ -1,13 +0,0 @@ -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 diff --git a/core/jvmTest/src/kotlinx/serialization/test/TypeToken.kt b/core/jvmTest/src/kotlinx/serialization/test/TypeToken.kt new file mode 100644 index 000000000..e4fb5be43 --- /dev/null +++ b/core/jvmTest/src/kotlinx/serialization/test/TypeToken.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.test + +import java.lang.reflect.* + +// Same classes are present in SerializerByTypeTest.kt, +// but it seems that json-jvm-test does not depend on core-jvm-test + +@PublishedApi +internal open class TypeBase + +public inline fun typeTokenOf(): Type { + val base = object : TypeBase() {} + val superType = base::class.java.genericSuperclass!! + return (superType as ParameterizedType).actualTypeArguments.first()!! +} diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt index 20b626f21..1e8aef42d 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.features @@ -9,7 +9,8 @@ import kotlinx.serialization.builtins.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.json.* -import kotlinx.serialization.test.InternalHexConverter +import kotlinx.serialization.modules.* +import kotlinx.serialization.test.* import kotlin.test.* class CheckedData(val data: T, val checkSum: ByteArray) { @@ -70,6 +71,9 @@ data class DataWithString(@Serializable(with = CheckedDataSerializer::class) val @Serializable data class DataWithInt(@Serializable(with = CheckedDataSerializer::class) val data: CheckedData) +@Serializable +data class DataWithStringContext(@Contextual val data: CheckedData) + class GenericCustomSerializerTest { @Test @@ -89,4 +93,18 @@ class GenericCustomSerializerTest { val restored = Json.decodeFromString(DataWithInt.serializer(), s) assertEquals(original, restored) } + + + @Test + fun testContextualGeneric() { + val module = SerializersModule { + contextual(CheckedData::class) { args -> CheckedDataSerializer(args[0] as KSerializer)} + } + assertStringFormAndRestored( + """{"data":{"data":"my data","checkSum":"2A20"}}""", + DataWithStringContext(CheckedData("my data", byteArrayOf(42, 32))), + DataWithStringContext.serializer(), + Json { serializersModule = module } + ) + } } From f89eca27dff4c473617853cb8a9328d67fa008ad Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Wed, 14 Apr 2021 21:08:07 +0300 Subject: [PATCH 3/7] ~ change array type to list --- .../serialization/ContextualSerializer.kt | 4 ++-- .../src/kotlinx/serialization/Serializers.kt | 6 +++--- .../serialization/modules/SerializersModule.kt | 16 ++++++++-------- .../modules/SerializersModuleBuilders.kt | 2 +- .../modules/SerializersModuleCollector.kt | 4 ++-- .../modules/ContextualGenericsTest.kt | 2 +- .../src/kotlinx/serialization/SerializersJvm.kt | 6 +++--- .../json/internal/PolymorphismValidator.kt | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt index ee75c36f1..7ab9f56e8 100644 --- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization @@ -46,7 +46,7 @@ public class ContextualSerializer( ) : KSerializer { private fun serializer(serializersModule: SerializersModule): KSerializer = - serializersModule.getContextual(serializableClass, typeArgumentsSerializers) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() + serializersModule.getContextual(serializableClass, typeArgumentsSerializers.asList()) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() // Used from auto-generated code public constructor(serializableClass: KClass) : this(serializableClass, null, EMPTY_SERIALIZER_ARRAY) diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index 9b14d84b2..8e2ecffad 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -116,14 +116,14 @@ private fun SerializersModule.builtinSerializer( } val args = serializers.toTypedArray() rootClass.constructSerializerForGivenTypeArgs(*args) - ?: reflectiveOrContextual(rootClass, args) + ?: reflectiveOrContextual(rootClass, serializers) } } } @OptIn(ExperimentalSerializationApi::class) -internal fun SerializersModule.reflectiveOrContextual(kClass: KClass, typeParameterSerializers: Array>): KSerializer? { - return kClass.serializerOrNull() ?: getContextual(kClass, typeParameterSerializers as Array>) +internal fun SerializersModule.reflectiveOrContextual(kClass: KClass, typeParameterSerializers: List>): KSerializer? { + return kClass.serializerOrNull() ?: getContextual(kClass, typeParameterSerializers) } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index e021a2d7a..18d9d3cc9 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -37,12 +37,12 @@ public sealed class SerializersModule { ) @LowPriorityInOverloadResolution public fun getContextual(kclass: KClass): KSerializer? = - getContextual(kclass, EMPTY_SERIALIZER_ARRAY) + getContextual(kclass, emptyList()) @ExperimentalSerializationApi public abstract fun getContextual( kClass: KClass, - typeArgumentsSerializers: Array> = EMPTY_SERIALIZER_ARRAY + typeArgumentsSerializers: List> = emptyList() ): KSerializer? /** @@ -99,7 +99,7 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri override fun contextual( kClass: KClass, - provider: (serializers: Array>) -> KSerializer<*> + provider: (serializers: List>) -> KSerializer<*> ) { registerSerializer(kClass, ContextualProvider.WithTypeArguments(provider), allowOverwrite = true) } @@ -149,7 +149,7 @@ internal class SerialModuleImpl( return (polyBase2DefaultProvider[baseClass] as? PolymorphicProvider)?.invoke(serializedClassName) } - override fun getContextual(kClass: KClass, typeArgumentsSerializers: Array>): KSerializer? { + override fun getContextual(kClass: KClass, typeArgumentsSerializers: List>): KSerializer? { return (class2ContextualFactory[kClass]?.invoke(typeArgumentsSerializers)) as? KSerializer? } @@ -191,19 +191,19 @@ internal typealias PolymorphicProvider = (className: String?) -> Deseriali * ``` */ internal sealed class ContextualProvider { - abstract operator fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> + abstract operator fun invoke(typeArgumentsSerializers: List>): KSerializer<*> class Argless(val serializer: KSerializer<*>) : ContextualProvider() { - override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = serializer + override fun invoke(typeArgumentsSerializers: List>): KSerializer<*> = serializer override fun equals(other: Any?): Boolean = other is Argless && other.serializer == this.serializer override fun hashCode(): Int = serializer.hashCode() } - class WithTypeArguments(val provider: (typeArgumentsSerializers: Array>) -> KSerializer<*>) : + class WithTypeArguments(val provider: (typeArgumentsSerializers: List>) -> KSerializer<*>) : ContextualProvider() { - override fun invoke(typeArgumentsSerializers: Array>): KSerializer<*> = + override fun invoke(typeArgumentsSerializers: List>): KSerializer<*> = provider(typeArgumentsSerializers) } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index b2625648a..1ac9307f8 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -53,7 +53,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser public override fun contextual( kClass: KClass, - provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> ): Unit = registerSerializer(kClass, ContextualProvider.WithTypeArguments(provider)) /** diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index a68f57379..8d2e2efcd 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @file:Suppress("RedundantVisibilityModifier") @@ -30,7 +30,7 @@ public interface SerializersModuleCollector { public fun contextual( kClass: KClass, - provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> ) /** diff --git a/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt b/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt index e08a2903c..cddb1ceed 100644 --- a/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt +++ b/core/commonTest/src/kotlinx/serialization/modules/ContextualGenericsTest.kt @@ -65,7 +65,7 @@ open class ContextualGenericsTest { @Test fun testModuleProvidesMultipleGenericSerializers() { fun checkFor(serial: KSerializer<*>) { - val serializer = serializersModuleWithProvider.getContextual(ThirdPartyBox::class, arrayOf(serial))?.descriptor + val serializer = serializersModuleWithProvider.getContextual(ThirdPartyBox::class, listOf(serial))?.descriptor assertEquals(serial.descriptor, serializer?.getElementDescriptor(0)) } checkFor(Item.serializer()) diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt index 7fc4add7f..b4aa980ff 100644 --- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt +++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt @@ -112,8 +112,8 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing else -> { // probably we should deprecate this method because it can't differ nullable vs non-nullable types // since it uses Java TypeToken, not Kotlin one - val varargs = argsSerializers.map { it as KSerializer }.toTypedArray() - (rootClass.kotlin.constructSerializerForGivenTypeArgs(*varargs) as? KSerializer) + val varargs = argsSerializers.map { it as KSerializer } + (rootClass.kotlin.constructSerializerForGivenTypeArgs(*(varargs.toTypedArray())) as? KSerializer) ?: reflectiveOrContextual(rootClass.kotlin as KClass, varargs) } } @@ -125,7 +125,7 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing @OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeArgSerializer: Boolean): KSerializer? { return if (!type.isArray) { - reflectiveOrContextual(type.kotlin as KClass, emptyArray()) + reflectiveOrContextual(type.kotlin as KClass, emptyList()) } else { val eType: Class<*> = type.componentType val s = if (failOnMissingTypeArgSerializer) serializer(eType) else (serializerOrNull(eType) ?: return null) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt index 138310b17..04cdb045b 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.json.internal @@ -17,7 +17,7 @@ internal class PolymorphismValidator( override fun contextual( kClass: KClass, - provider: (typeArgumentsSerializers: Array>) -> KSerializer<*> + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> ) { // Nothing here } From 4e1433141f7479ebfdae7eed7205383f5c54aa46 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Wed, 14 Apr 2021 21:39:43 +0300 Subject: [PATCH 4/7] fix api dump --- core/api/kotlinx-serialization-core.api | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index ab010191f..53b2ee644 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -1157,8 +1157,8 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder { public abstract class kotlinx/serialization/modules/SerializersModule { public abstract fun dumpTo (Lkotlinx/serialization/modules/SerializersModuleCollector;)V public final fun getContextual (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; - public abstract fun getContextual (Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; - public static synthetic fun getContextual$default (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;ILjava/lang/Object;)Lkotlinx/serialization/KSerializer; + public abstract fun getContextual (Lkotlin/reflect/KClass;Ljava/util/List;)Lkotlinx/serialization/KSerializer; + public static synthetic fun getContextual$default (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;Ljava/util/List;ILjava/lang/Object;)Lkotlinx/serialization/KSerializer; public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy; public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy; } From c9d40e7c2454b0d0925e69e351e09d336a1e52af Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Thu, 15 Apr 2021 16:36:34 +0300 Subject: [PATCH 5/7] ~ rename param --- core/commonMain/src/kotlinx/serialization/Serializers.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index 8e2ecffad..97a00e6a2 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -122,8 +122,8 @@ private fun SerializersModule.builtinSerializer( } @OptIn(ExperimentalSerializationApi::class) -internal fun SerializersModule.reflectiveOrContextual(kClass: KClass, typeParameterSerializers: List>): KSerializer? { - return kClass.serializerOrNull() ?: getContextual(kClass, typeParameterSerializers) +internal fun SerializersModule.reflectiveOrContextual(kClass: KClass, typeArgumentsSerializers: List>): KSerializer? { + return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers) } From bc212c4720e9b50d5ba7b61b845a762dcf08ea9a Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Fri, 16 Apr 2021 20:40:49 +0300 Subject: [PATCH 6/7] ~ improve documentation --- core/api/kotlinx-serialization-core.api | 2 +- .../serialization/ContextualSerializer.kt | 9 ++++--- .../modules/SerializersModule.kt | 17 ++++++------ .../modules/SerializersModuleBuilders.kt | 22 ++++++++++++++- .../modules/SerializersModuleCollector.kt | 5 +++- docs/serialization-guide.md | 1 + docs/serializers.md | 27 ++++++++++++++++++- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 53b2ee644..a7004ddf7 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -1156,7 +1156,7 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder { public abstract class kotlinx/serialization/modules/SerializersModule { public abstract fun dumpTo (Lkotlinx/serialization/modules/SerializersModuleCollector;)V - public final fun getContextual (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; + public final synthetic fun getContextual (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; public abstract fun getContextual (Lkotlin/reflect/KClass;Ljava/util/List;)Lkotlinx/serialization/KSerializer; public static synthetic fun getContextual$default (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;Ljava/util/List;ILjava/lang/Object;)Lkotlinx/serialization/KSerializer; public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy; diff --git a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt index 7ab9f56e8..4cda7b1a6 100644 --- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt @@ -42,13 +42,16 @@ import kotlin.reflect.* public class ContextualSerializer( private val serializableClass: KClass, private val fallbackSerializer: KSerializer?, - private val typeArgumentsSerializers: Array> + typeArgumentsSerializers: Array> ) : KSerializer { + private val typeArgumentsSerializers: List> = typeArgumentsSerializers.asList() + private fun serializer(serializersModule: SerializersModule): KSerializer = - serializersModule.getContextual(serializableClass, typeArgumentsSerializers.asList()) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() + serializersModule.getContextual(serializableClass, typeArgumentsSerializers) ?: fallbackSerializer ?: serializableClass.serializerNotRegistered() - // Used from auto-generated code + // Used from the old plugins + @Suppress("unused") public constructor(serializableClass: KClass) : this(serializableClass, null, EMPTY_SERIALIZER_ARRAY) public override val descriptor: SerialDescriptor = diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 18d9d3cc9..4dc49f1c2 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -6,7 +6,6 @@ package kotlinx.serialization.modules import kotlinx.serialization.* import kotlinx.serialization.internal.* -import kotlin.internal.* import kotlin.jvm.* import kotlin.native.concurrent.* import kotlin.reflect.* @@ -24,21 +23,23 @@ import kotlin.reflect.* */ public sealed class SerializersModule { - /** - * Returns a contextual serializer associated with a given [kclass]. - * This method is used in context-sensitive operations on a property marked with [Contextual] by a [ContextualSerializer] - */ @ExperimentalSerializationApi - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @Deprecated( "Deprecated in favor of overload with default parameter", ReplaceWith("getContextual(kclass)"), - DeprecationLevel.ERROR + DeprecationLevel.HIDDEN ) - @LowPriorityInOverloadResolution public fun getContextual(kclass: KClass): KSerializer? = getContextual(kclass, emptyList()) + /** + * Returns a contextual serializer associated with a given [kClass]. + * If given class has generic parameters and module has provider for [kClass], + * [typeArgumentsSerializers] are used to create serializer. + * This method is used in context-sensitive operations on a property marked with [Contextual] by a [ContextualSerializer]. + * + * @see SerializersModuleBuilder.contextual + */ @ExperimentalSerializationApi public abstract fun getContextual( kClass: KClass, diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 1ac9307f8..f81f27fc1 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -45,12 +45,32 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser /** * Adds [serializer] associated with given [kClass] for contextual serialization. - * Throws [SerializationException] if a module already has serializer associated with a [kClass]. + * If [kClass] has generic type parameters, consider registering provider instead. + * + * Throws [SerializationException] if a module already has serializer or provider associated with a [kClass]. * To overwrite an already registered serializer, [SerializersModule.overwriteWith] can be used. */ public override fun contextual(kClass: KClass, serializer: KSerializer): Unit = registerSerializer(kClass, ContextualProvider.Argless(serializer)) + /** + * Registers [provider] associated with given generic [kClass] for contextual serialization. + * When a serializer is requested from a module, provider is being called with type arguments serializers + * of the particular [kClass] usage. + * + * Example: + * ``` + * class Holder(@Contextual val boxI: Box, @Contextual val boxS: Box) + * + * val module = SerializersModule { + * // args[0] contains Int.serializer() or String.serializer(), depending on the property + * contextual(Box::class) { args -> BoxSerializer(args[0]) } + * } + * ``` + * + * Throws [SerializationException] if a module already has provider or serializer associated with a [kClass]. + * To overwrite an already registered serializer, [SerializersModule.overwriteWith] can be used. + */ public override fun contextual( kClass: KClass, provider: (typeArgumentsSerializers: List>) -> KSerializer<*> diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index 8d2e2efcd..10ebc3b1f 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -25,9 +25,12 @@ public interface SerializersModuleCollector { * Accept a serializer, associated with [kClass] for contextual serialization. */ public fun contextual(kClass: KClass, serializer: KSerializer): Unit = - contextual(kClass) { _ -> serializer } + contextual(kClass) { serializer } + /** + * Accept a provider, associated with generic [kClass] for contextual serialization. + */ public fun contextual( kClass: KClass, provider: (typeArgumentsSerializers: List>) -> KSerializer<*> diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md index d631860ae..c238a0595 100644 --- a/docs/serialization-guide.md +++ b/docs/serialization-guide.md @@ -73,6 +73,7 @@ Once the project is set up, we can start serializing some classes. * [Format-specific serializers](serializers.md#format-specific-serializers) * [Contextual serialization](serializers.md#contextual-serialization) * [Serializers module](serializers.md#serializers-module) + * [Contextual serialization and generic classes](serializers.md#contextual-serialization-and-generic-classes) * [Deriving external serializer for another Kotlin class (experimental)](serializers.md#deriving-external-serializer-for-another-kotlin-class-experimental) * [External serialization uses properties](serializers.md#external-serialization-uses-properties) diff --git a/docs/serializers.md b/docs/serializers.md index b9a860244..818d7dfd9 100644 --- a/docs/serializers.md +++ b/docs/serializers.md @@ -28,6 +28,7 @@ In this chapter we'll take a look at serializers in more detail, and we'll see h * [Format-specific serializers](#format-specific-serializers) * [Contextual serialization](#contextual-serialization) * [Serializers module](#serializers-module) + * [Contextual serialization and generic classes](#contextual-serialization-and-generic-classes) * [Deriving external serializer for another Kotlin class (experimental)](#deriving-external-serializer-for-another-kotlin-class-experimental) * [External serialization uses properties](#external-serialization-uses-properties) @@ -886,7 +887,7 @@ class this serializer is defined for is fetched automatically via the `reified` private val module = SerializersModule { contextual(DateAsLongSerializer) } -``` +``` Next we create an instance of the [Json] format with this module using the [Json {}][Json()] builder function and the [serializersModule][JsonBuilder.serializersModule] property. @@ -914,6 +915,30 @@ fun main() { +### Contextual serialization and generic classes + +In the previous section we saw that we can register serializer instance in the module for a class we want to serialize contextually. +We also know that [serializers for generic classes have constructor parameters](#custom-serializers-for-a-generic-type) — type arguments serializers. +It means that we can't use one serializer instance for a class if this class is generic: + +```kotlin +val incorrectModule = SerializersModule { + // Can serialize only Box, but not Box or others + contextual(BoxSerializer(Int.serializer())) +} +``` + +For cases when one want to serialize contextually a generic class, it is possible to register provider in the module: + +```kotlin +val correctModule = SerializersModule { + // args[0] contains Int.serializer() or String.serializer(), depending on the usage + contextual(Box::class) { args -> BoxSerializer(args[0]) } +} +``` + + + > Additional details on serialization modules are given in > the [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) section of > the [Polymorphism](polymorphism.md) chapter. From de8d05da15d663b8a719a652e6ca043f52f49f7a Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Tue, 20 Apr 2021 14:46:41 +0300 Subject: [PATCH 7/7] Update core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt Co-authored-by: Vsevolod Tolstopyatov --- .../src/kotlinx/serialization/modules/SerializersModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 4dc49f1c2..5d9211731 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -28,7 +28,7 @@ public sealed class SerializersModule { "Deprecated in favor of overload with default parameter", ReplaceWith("getContextual(kclass)"), DeprecationLevel.HIDDEN - ) + ) // Was stable since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner public fun getContextual(kclass: KClass): KSerializer? = getContextual(kclass, emptyList())