Kotlinx.serialization: @Polymorphic annotation does not work for properties when used on type

Created on 5 May 2019  路  5Comments  路  Source: Kotlin/kotlinx.serialization

Describe the bug

I have a class hierarchy with several inheritance hierarchies. In this hierarchy, members marked as Polymorphic do not work.

To Reproduce

Here is a test:

import kotlinx.serialization.Polymorphic
import kotlinx.serialization.PolymorphicSerializer
import kotlin.test.Test
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlin.test.assertEquals

@Serializable
sealed class Fruit

@Serializable
data class Banana(val name: String) : Fruit()
@Serializable
data class Apple(val id: Int) : Fruit()

@Serializable
sealed class Box

@Serializable
data class SmallBox(val content: @Polymorphic Fruit): Box()

@Serializable
data class LargeBox(val content: List<@Polymorphic Fruit>): Box()

class Test {
    @Test
    fun test() {
        val format = Json(context= SerializersModule { 
            polymorphic(Fruit::class) { 
                Banana::class with Banana.serializer()
                Apple::class with Apple.serializer()
            }
            polymorphic(Box::class) { 
                SmallBox::class with SmallBox.serializer()
                LargeBox::class with LargeBox.serializer()
            }
        })

        val data = SmallBox(Banana("Test"))
//        val data = LargeBox(listOf(Banana("Test"), Apple(22)))

        val asString = format.stringify(PolymorphicSerializer(Box::class), data)
        println(asString)
        val obj = format.parse(PolymorphicSerializer(Box::class), asString)
        assertEquals(data, obj)
    }
}

The printout is
{"type":"SmallBox","content":{}}
And the test fails with

java.lang.InstantiationError: Fruit
    at Fruit$$serializer.deserialize(BugTest.kt)
    at Fruit$$serializer.deserialize(BugTest.kt:10)
    at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:33)
    at kotlinx.serialization.json.internal.AbstractJsonTreeInput.decodeSerializableValue(TreeJsonInput.kt:48)
    at kotlinx.serialization.TaggedDecoder$decodeSerializableElement$1.invoke(Tagged.kt:257)
    at kotlinx.serialization.TaggedDecoder.tagBlock(Tagged.kt:270)
    at kotlinx.serialization.TaggedDecoder.decodeSerializableElement(Tagged.kt:257)
    at SmallBox$$serializer.deserialize(BugTest.kt)
    at SmallBox$$serializer.deserialize(BugTest.kt:21)
    at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:33)
    at kotlinx.serialization.json.internal.AbstractJsonTreeInput.decodeSerializableValue(TreeJsonInput.kt:48)
    at kotlinx.serialization.CoreKt.decode(Core.kt:79)
    at kotlinx.serialization.json.internal.TreeJsonInputKt.readJson(TreeJsonInput.kt:27)
    at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:41)
    at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:29)
    at kotlinx.serialization.CoreKt.decode(Core.kt:79)
    at kotlinx.serialization.json.Json.parse(Json.kt:148)
    at BugTest.test(BugTest.kt:45)

When I use the line with LargeBox instead of the SmallBox line, so, polymorphic elements in a list, it works as expected.

Expected behavior
The printout should be
{"type":"SmallBox","content":{"type":"Banana","name":"Test"}}

Environment

  • Kotlin version: 1.3.30
  • Library version: 0.11.0
  • Kotlin platforms: JVM, I did not try other platforms
  • Gradle version: 5.2.1
  • JVM: 1.8.0_201 (Oracle Corporation 25.201-b09)
  • OS: Linux 4.20.6-200.fc29.x86_64 amd64
bug compiler-plugin

Most helpful comment

@danielebart Yes, registering is now necessary, since polymorphic serialization is now multiplatform and JS and Native lack a necessary amount of reflection (like Class.forName) to retrieve serializers without registering.

Regarding sealed classes, we have plans to improve their polymorphic serialization (since all their inheritors indeed are known in compile-time and therefore can be registered automatically)

All 5 comments

My guess: I think you base classes are supposed to be abstract. So Fruit and Box, should be abstract... since Fruit is not abstract, it looks it not serializing Banana, instead it is serializing Fruit and not writing type info

Edit:
I ran you test with Fruit and Box as abstract instead of sealed and it passes @campino

I can confirm that it works if I use 'abstract' instead of 'sealed'. However, I still think that this is a bug.

  • the behaviour is different for list members and class members (LargeBox works even with sealed).
  • when I use 'sealed abstract class', the kotlin compiler gives me a warning, saying 'Modifier 'abstract' is redundant because 'sealed' is present', but if it is redundant, it should work with sealed just as well.

Hm, It should be data class SmallBox(@Polymorphic val content: Fruit) (annotation on the property, not on the type).

However, this is still a compiler plugin bug since it is reasonable to recognize here annotations on types too.

@sandwwraith is it really necessary to define a SerialModule for polymorphic classes?
i have this class hierarchy:

@Polymorphic
sealed class A {

    @Serializable
    data class B(val foo: String): A()

    @Serializable
    data class C(val foo: String): A()
}

this is not working without registering a SerialModule (class B is not registered for polymorphic serialization in the scope of class A), am i missing something? i seem to remember that in the previous release this step wasn't necessary

@danielebart Yes, registering is now necessary, since polymorphic serialization is now multiplatform and JS and Native lack a necessary amount of reflection (like Class.forName) to retrieve serializers without registering.

Regarding sealed classes, we have plans to improve their polymorphic serialization (since all their inheritors indeed are known in compile-time and therefore can be registered automatically)

Was this page helpful?
0 / 5 - 0 ratings