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
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.
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)
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)