summaryrefslogtreecommitdiff
path: root/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt')
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt149
1 files changed, 149 insertions, 0 deletions
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
new file mode 100644
index 00000000..77004dbc
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.descriptors.*
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
+import kotlin.reflect.*
+import kotlin.test.*
+
+class PolymorphicOnClassesTest {
+
+ // this has implicit @Polymorphic
+ interface IMessage {
+ val body: String
+ }
+
+ // and this class too has implicit @Polymorphic
+ @Serializable
+ abstract class Message : IMessage {
+ abstract override val body: String
+ }
+
+ @Polymorphic
+ @Serializable
+ @SerialName("SimpleMessage") // to cut out package prefix
+ open class SimpleMessage : Message() {
+ override var body: String = "Simple"
+ }
+
+ @Serializable
+ @SerialName("DoubleSimpleMessage")
+ class DoubleSimpleMessage(val body2: String) : SimpleMessage()
+
+ @Serializable
+ @SerialName("MessageWithId")
+ open class MessageWithId(val id: Int, override val body: String) : Message()
+
+ @Serializable
+ class Holder(
+ val iMessage: IMessage,
+ val iMessageList: List<IMessage>,
+ val message: Message,
+ val msgSet: Set<Message>,
+ val simple: SimpleMessage,
+ // all above should be polymorphic
+ val withId: MessageWithId // but this not
+ )
+
+
+ private fun genTestData(): Holder {
+ var cnt = -1
+ fun gen(): MessageWithId {
+ cnt++
+ return MessageWithId(cnt, "Message #$cnt")
+ }
+
+ return Holder(gen(), listOf(gen(), gen()), gen(), setOf(SimpleMessage()), DoubleSimpleMessage("DoubleSimple"), gen())
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private val testModule = SerializersModule {
+ listOf(Message::class, IMessage::class, SimpleMessage::class).forEach { clz ->
+ polymorphic(clz as KClass<IMessage>) {
+ subclass(SimpleMessage.serializer())
+ subclass(DoubleSimpleMessage.serializer())
+ subclass(MessageWithId.serializer())
+ }
+ }
+ }
+
+ @Test
+ fun testEnablesImplicitlyOnInterfacesAndAbstractClasses() {
+ val json = Json {
+ prettyPrint = false
+ useArrayPolymorphism = true
+ serializersModule = testModule
+ encodeDefaults = true
+ }
+ val data = genTestData()
+ assertEquals(
+ """{"iMessage":["MessageWithId",{"id":0,"body":"Message #0"}],""" +
+ """"iMessageList":[["MessageWithId",{"id":1,"body":"Message #1"}],""" +
+ """["MessageWithId",{"id":2,"body":"Message #2"}]],"message":["MessageWithId",{"id":3,"body":"Message #3"}],""" +
+ """"msgSet":[["SimpleMessage",{"body":"Simple"}]],"simple":["DoubleSimpleMessage",{"body":"Simple",""" +
+ """"body2":"DoubleSimple"}],"withId":{"id":4,"body":"Message #4"}}""",
+ json.encodeToString(Holder.serializer(), data)
+ )
+ }
+
+ @Test
+ fun testDescriptor() {
+ val polyDesc = Holder.serializer().descriptor.elementDescriptors.first()
+ assertEquals(PolymorphicSerializer(IMessage::class).descriptor, polyDesc)
+ assertEquals(2, polyDesc.elementsCount)
+ assertEquals(PrimitiveKind.STRING, polyDesc.getElementDescriptor(0).kind)
+ }
+
+ private fun SerialDescriptor.inContext(module: SerializersModule): List<SerialDescriptor> = when (kind) {
+ PolymorphicKind.OPEN -> module.getPolymorphicDescriptors(this)
+ else -> error("Expected this function to be called on OPEN descriptor")
+ }
+
+ @Test
+ fun testResolvePolymorphicDescriptor() {
+ val polyDesc = Holder.serializer().descriptor.elementDescriptors.first() // iMessage: IMessage
+
+ assertEquals(PolymorphicKind.OPEN, polyDesc.kind)
+
+ val inheritors = polyDesc.inContext(testModule)
+ val names = listOf("SimpleMessage", "DoubleSimpleMessage", "MessageWithId").toSet()
+ assertEquals(names, inheritors.map(SerialDescriptor::serialName).toSet(), "Expected correct inheritor names")
+ assertTrue(inheritors.all { it.kind == StructureKind.CLASS }, "Expected all inheritors to be CLASS")
+ }
+
+ @Test
+ fun testDocSampleWithAllDistinct() {
+ fun allDistinctNames(descriptor: SerialDescriptor, module: SerializersModule) = when (descriptor.kind) {
+ is PolymorphicKind.OPEN -> module.getPolymorphicDescriptors(descriptor)
+ .map { it.elementNames.toList() }.flatten().toSet()
+ is SerialKind.CONTEXTUAL -> module.getContextualDescriptor(descriptor)?.elementNames?.toList().orEmpty().toSet()
+ else -> descriptor.elementNames.toSet()
+ }
+
+ val polyDesc = Holder.serializer().descriptor.elementDescriptors.first() // iMessage: IMessage
+ assertEquals(setOf("id", "body", "body2"), allDistinctNames(polyDesc, testModule))
+ assertEquals(setOf("id", "body"), allDistinctNames(MessageWithId.serializer().descriptor, testModule))
+ }
+
+ @Test
+ fun testSerializerLookupForInterface() {
+ // On JVM and JS IR it can be supported via reflection/runtime hacks
+ // on Native, unfortunately, only with intrinsics.
+ if (isNative() || isWasm()) return
+ val msgSer = serializer<IMessage>()
+ assertEquals(IMessage::class, (msgSer as AbstractPolymorphicSerializer).baseClass)
+ }
+
+ @Test
+ fun testSerializerLookupForAbstractClass() {
+ val absSer = serializer<Message>()
+ assertEquals(Message::class, (absSer as AbstractPolymorphicSerializer).baseClass)
+ }
+}