summaryrefslogtreecommitdiff
path: root/formats/json/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
blob: 8f00ad97d9b370ef5c158494b2adee3991051e5d (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
/*
 * Copyright 2017-2020 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.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.test.*
import kotlin.test.*

class ContextAndPolymorphicTest {

    @Serializable
    data class Data(val a: Int, val b: Int = 42)

    @Serializable
    data class EnhancedData(
        val data: Data,
        @Contextual val stringPayload: Payload,
        @Serializable(with = BinaryPayloadSerializer::class) val binaryPayload: Payload
    )

    @Serializable
    @SerialName("Payload")
    data class Payload(val s: String)

    @Serializable
    data class PayloadList(val ps: List<@Contextual Payload>)

    @Serializer(forClass = Payload::class)
    object PayloadSerializer

    object BinaryPayloadSerializer : KSerializer<Payload> {
        override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BinaryPayload", PrimitiveKind.STRING)

        override fun serialize(encoder: Encoder, value: Payload) {
            encoder.encodeString(InternalHexConverter.printHexBinary(value.s.encodeToByteArray()))
        }

        override fun deserialize(decoder: Decoder): Payload {
            return Payload(InternalHexConverter.parseHexBinary(decoder.decodeString()).decodeToString())
        }
    }

    private val value = EnhancedData(Data(100500), Payload("string"), Payload("binary"))
    private lateinit var json: Json

    @BeforeTest
    fun initContext() {
        val scope = serializersModuleOf(Payload::class, PayloadSerializer)
        val bPolymorphicModule = SerializersModule { polymorphic(Any::class) { subclass(PayloadSerializer) } }
        json = Json {
            useArrayPolymorphism = true
            encodeDefaults = true
            serializersModule = scope + bPolymorphicModule
        }
    }

    @Test
    fun testWriteCustom() {
        val s = json.encodeToString(EnhancedData.serializer(), value)
        assertEquals("""{"data":{"a":100500,"b":42},"stringPayload":{"s":"string"},"binaryPayload":"62696E617279"}""", s)
    }

    @Test
    fun testReadCustom() {
        val s = json.decodeFromString(EnhancedData.serializer(),
            """{"data":{"a":100500,"b":42},"stringPayload":{"s":"string"},"binaryPayload":"62696E617279"}""")
        assertEquals(value, s)
    }

    @Test
    fun testWriteCustomList() {
        val s = json.encodeToString(PayloadList.serializer(), PayloadList(listOf(Payload("1"), Payload("2"))))
        assertEquals("""{"ps":[{"s":"1"},{"s":"2"}]}""", s)
    }

    @Test
    fun testPolymorphicResolve() {
        val map = mapOf<String, Any>("Payload" to Payload("data"))
        val serializer = MapSerializer(String.serializer(), PolymorphicSerializer(Any::class))
        val s = json.encodeToString(serializer, map)
        assertEquals("""{"Payload":["Payload",{"s":"data"}]}""", s)
    }

    @Test
    fun testDifferentRepresentations() {
        val simpleModule = serializersModuleOf(PayloadSerializer)
        val binaryModule = serializersModuleOf(BinaryPayloadSerializer)

        val json1 = Json { useArrayPolymorphism = true; serializersModule = simpleModule }
        val json2 = Json { useArrayPolymorphism = true; serializersModule = binaryModule }

        // in json1, Payload would be serialized with PayloadSerializer,
        // in json2, Payload would be serialized with BinaryPayloadSerializer

        val list = PayloadList(listOf(Payload("string")))
        assertEquals("""{"ps":[{"s":"string"}]}""", json1.encodeToString(PayloadList.serializer(), list))
        assertEquals("""{"ps":["737472696E67"]}""", json2.encodeToString(PayloadList.serializer(), list))
    }

    private fun SerialDescriptor.inContext(module: SerializersModule): SerialDescriptor = when (kind) {
        SerialKind.CONTEXTUAL -> requireNotNull(module.getContextualDescriptor(this)) { "Expected $this to be registered in module" }
        else -> error("Expected this function to be called on CONTEXTUAL descriptor")
    }

    @Test
    fun testResolveContextualDescriptor() {
        val simpleModule = serializersModuleOf(PayloadSerializer)
        val binaryModule = serializersModuleOf(BinaryPayloadSerializer)

        val contextDesc = EnhancedData.serializer().descriptor.elementDescriptors.toList()[1] // @ContextualSer stringPayload
        assertEquals(SerialKind.CONTEXTUAL, contextDesc.kind)
        assertEquals(0, contextDesc.elementsCount)

        val resolvedToDefault = contextDesc.inContext(simpleModule)
        assertEquals(StructureKind.CLASS, resolvedToDefault.kind)
        assertEquals("Payload", resolvedToDefault.serialName)
        assertEquals(1, resolvedToDefault.elementsCount)

        val resolvedToBinary = contextDesc.inContext(binaryModule)
        assertEquals(PrimitiveKind.STRING, resolvedToBinary.kind)
        assertEquals("BinaryPayload", resolvedToBinary.serialName)
    }

    @Test
    fun testContextualSerializerUsesDefaultIfModuleIsEmpty() {
        val s = Json { useArrayPolymorphism = true; encodeDefaults = true }.encodeToString(EnhancedData.serializer(), value)
        assertEquals("""{"data":{"a":100500,"b":42},"stringPayload":{"s":"string"},"binaryPayload":"62696E617279"}""", s)
    }
}