summaryrefslogtreecommitdiff
path: root/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeBaseTest.kt
blob: 8fcd549977635712a1de8b74f4013cc1c37504a7 (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
150
151
152
153
/*
 * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization.json.polymorphic

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.test.*

abstract class JsonClassDiscriminatorModeBaseTest(
    val discriminator: ClassDiscriminatorMode,
    val deserializeBack: Boolean = true
) : JsonTestBase() {

    @Serializable
    sealed class SealedBase

    @Serializable
    @SerialName("container")
    data class SealedContainer(val i: Inner): SealedBase()

    @Serializable
    @SerialName("inner")
    data class Inner(val x: String, val e: SampleEnum = SampleEnum.OptionB)

    @Serializable
    @SerialName("outer")
    data class Outer(val inn: Inner, val lst: List<Inner>, val lss: List<String>)

    data class ContextualType(val text: String)

    object CtxSerializer : KSerializer<ContextualType> {
        override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CtxSerializer") {
            element("a", String.serializer().descriptor)
            element("b", String.serializer().descriptor)
        }

        override fun serialize(encoder: Encoder, value: ContextualType) {
            encoder.encodeStructure(descriptor) {
                encodeStringElement(descriptor, 0, value.text.substringBefore("#"))
                encodeStringElement(descriptor, 1, value.text.substringAfter("#"))
            }
        }

        override fun deserialize(decoder: Decoder): ContextualType {
            lateinit var a: String
            lateinit var b: String
            decoder.decodeStructure(descriptor) {
                while (true) {
                    when (decodeElementIndex(descriptor)) {
                        0 -> a = decodeStringElement(descriptor, 0)
                        1 -> b = decodeStringElement(descriptor, 1)
                        else -> break
                    }
                }
            }
            return ContextualType("$a#$b")
        }
    }

    @Serializable
    @SerialName("withContextual")
    data class WithContextual(@Contextual val ctx: ContextualType, val i: Inner)

    val ctxModule = serializersModuleOf(CtxSerializer)

    val json = Json(default) {
        ignoreUnknownKeys = true
        serializersModule = polymorphicTestModule + ctxModule
        encodeDefaults = true
        classDiscriminatorMode = discriminator
    }

    @Serializable
    @SerialName("mixed")
    data class MixedPolyAndRegular(val sb: SealedBase, val sc: SealedContainer, val i: Inner)

    private inline fun <reified T> doTest(expected: String, obj: T) {
        parametrizedTest { mode ->
            val serialized = json.encodeToString(serializer<T>(), obj, mode)
            assertEquals(expected, serialized, "Failed with mode = $mode")
            if (deserializeBack) {
                val deserialized: T = json.decodeFromString(serializer(), serialized, mode)
                assertEquals(obj, deserialized, "Failed with mode = $mode")
            }
        }
    }

    fun testMixed(expected: String) {
        val i = Inner("in", SampleEnum.OptionC)
        val o = MixedPolyAndRegular(SealedContainer(i), SealedContainer(i), i)
        doTest(expected, o)
    }

    fun testIncludeNonPolymorphic(expected: String) {
        val o = Outer(Inner("X"), listOf(Inner("a"), Inner("b")), listOf("foo"))
        doTest(expected, o)
    }

    fun testIncludePolymorphic(expected: String) {
        val o = OuterNullableBox(OuterNullableImpl(InnerImpl(42), null), InnerImpl2(239))
        doTest(expected, o)
    }

    fun testIncludeSealed(expected: String) {
        val b = Box<SealedBase>(SealedContainer(Inner("x", SampleEnum.OptionC)))
        doTest(expected, b)
    }

    fun testContextual(expected: String) {
        val c = WithContextual(ContextualType("c#d"), Inner("x"))
        doTest(expected, c)
    }

    @Serializable
    @JsonClassDiscriminator("message_type")
    sealed class Base

    @Serializable // Class discriminator is inherited from Base
    sealed class ErrorClass : Base()

    @Serializable
    @SerialName("ErrorClassImpl")
    data class ErrorClassImpl(val msg: String) : ErrorClass()

    @Serializable
    @SerialName("Cont")
    data class Cont(val ec: ErrorClass, val eci: ErrorClassImpl)

    fun testCustomDiscriminator(expected: String) {
        val c = Cont(ErrorClassImpl("a"), ErrorClassImpl("b"))
        doTest(expected, c)
    }

    fun testTopLevelPolyImpl(expectedOpen: String, expectedSealed: String) {
        assertEquals(expectedOpen, json.encodeToString(InnerImpl(42)))
        assertEquals(expectedSealed, json.encodeToString(SealedContainer(Inner("x"))))
    }

    @Serializable
    @SerialName("NullableMixed")
    data class NullableMixed(val sb: SealedBase?, val sc: SealedContainer?)

    fun testNullable(expected: String) {
        val nm = NullableMixed(null, null)
       doTest(expected, nm)
    }
}