summaryrefslogtreecommitdiff
path: root/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
blob: 8e859ee2d79d1712e0d95a0087d7dae3c865b3cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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 (currentPlatform == Platform.NATIVE || currentPlatform == Platform.JS_LEGACY) 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)
    }
}