Skip to content

Using contextual serialization and encoding functions with reified type parameter doesn't work for generic interfaces #1645

@ShreckYe

Description

@ShreckYe

Describe the bug
Using contextual serialization and encoding functions with reified type parameters doesn't work for generic interfaces. It works if I pass a serializer manually or change the interface to an abstract class.

To Reproduce
Attach a code snippet or test data if possible.

Code:

@Serializable(with = OptionSerializer::class)
/*sealed*/ interface Option<out T>
class Some<out T>(val value: T) : Option<T>
object None : Option<Nothing>

@Serializable
class OptionSurrogate<out T>(val isPresent: Boolean, val value: T?)

fun <T> Option<T>.toSurrogate() =
    when (this) {
        is Some<T> -> OptionSurrogate(true, value)
        is None -> OptionSurrogate(false, null)
        else -> throw IllegalArgumentException()
    }

fun <T> OptionSurrogate<T>.toOption() =
    if (isPresent) Some(value!!)
    else None

class OptionSerializer<T>(valueSerializer: KSerializer<T>) : KSerializer<Option<T>> {
    val surrogateSerializer = OptionSurrogate.serializer(valueSerializer)
    override val descriptor: SerialDescriptor
        get() = surrogateSerializer.descriptor

    override fun serialize(encoder: Encoder, value: Option<T>) =
        encoder.encodeSerializableValue(surrogateSerializer, value.toSurrogate())

    override fun deserialize(decoder: Decoder): Option<T> =
        decoder.decodeSerializableValue(surrogateSerializer).toOption()
}

class Dummy

@Serializer(forClass = Dummy::class)
object DummySerializer

fun main() {
    val format0 = Json {
        serializersModule = SerializersModule {
        }
    }
    try {
        println(format0.encodeToString<Option<Dummy>>(Some(Dummy())))
    } catch (e: Exception) {
        e.printStackTrace()
    }

    val format1 = Json {
        serializersModule = SerializersModule {
            contextual(DummySerializer)
        }
    }
    try {
        println(format1.encodeToString<Option<Dummy>>(Some(Dummy())))
    } catch (e: Exception) {
        e.printStackTrace()
    }

    println(Json.encodeToString(OptionSerializer(DummySerializer), Some(Dummy())))
}

Output:

kotlinx.serialization.SerializationException: Serializer for class 'Dummy' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
	at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
	at kotlinx.serialization.internal.PlatformKt.platformSpecificSerializerNotRegistered(Platform.kt:29)
	at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:60)
	at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
	at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializer$SerializersKt__SerializersKt(Serializers.kt:96)
	at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:84)
	at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:59)
	at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
	at OptionKotlinxSerializationTestKt.main(OptionKotlinxSerializationTest.kt:118)
	at OptionKotlinxSerializationTestKt.main(OptionKotlinxSerializationTest.kt)
kotlinx.serialization.SerializationException: Serializer for class 'Option' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
	at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
	at kotlinx.serialization.internal.PlatformKt.platformSpecificSerializerNotRegistered(Platform.kt:29)
	at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:60)
	at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
	at OptionKotlinxSerializationTestKt.main(OptionKotlinxSerializationTest.kt:121)
	at OptionKotlinxSerializationTestKt.main(OptionKotlinxSerializationTest.kt)
{"isPresent":true,"value":{}}

As one can see, if I specify an empty SerializersModule, I get "Serializer for class 'Dummy' is not found."; if I add DummySerializer to it, I get "Serializer for class 'Option' is not found."; It works if I pass OptionSerializer(DummySerializer) manually.

It works with no problem if I change the Option interface to an open, abstract, or sealed class.

Expected behavior
{"isPresent":true,"value":{}} should be printed for the second println statement.

Environment

  • Kotlin version: [1.5.21]
  • Library version: [1.2.2]
  • Kotlin platforms: [JVM]
  • Gradle version: [7.1.1]
  • IDE version (if bug is related to the IDE) [IntelliJ IDEA 2021.2 (Community Edition), Build #IC-212.4746.92, built on July 27, 2021]
  • Other relevant context [OS version Windows 10, JRE version 11.0.11+9-b1504.13 amd64]

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions