• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.features
6 
7 import kotlinx.serialization.*
8 import kotlinx.serialization.builtins.*
9 import kotlinx.serialization.descriptors.*
10 import kotlinx.serialization.encoding.*
11 import kotlinx.serialization.json.*
12 import kotlinx.serialization.modules.*
13 import kotlinx.serialization.test.*
14 import kotlin.test.*
15 
16 class CheckedData<T : Any>(val data: T, val checkSum: ByteArray) {
17     override fun equals(other: Any?): Boolean {
18         if (this === other) return true
19         if (other == null || this::class != other::class) return false
20 
21         other as CheckedData<*>
22 
23         if (data != other.data) return false
24         if (!checkSum.contentEquals(other.checkSum)) return false
25 
26         return true
27     }
28 
29     override fun hashCode(): Int {
30         var result = data.hashCode()
31         result = 31 * result + checkSum.contentHashCode()
32         return result
33     }
34 }
35 
36 class CheckedDataSerializer<T : Any>(private val dataSerializer: KSerializer<T>) : KSerializer<CheckedData<T>> {
<lambda>null37     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CheckedDataSerializer") {
38         val dataDescriptor = dataSerializer.descriptor
39         element("data", dataDescriptor)
40         element("checkSum", ByteArraySerializer().descriptor)
41     }
42 
serializenull43     override fun serialize(encoder: Encoder, value: CheckedData<T>) {
44         val out = encoder.beginStructure(descriptor)
45         out.encodeSerializableElement(descriptor, 0, dataSerializer, value.data)
46         out.encodeStringElement(descriptor, 1, InternalHexConverter.printHexBinary(value.checkSum))
47         out.endStructure(descriptor)
48     }
49 
deserializenull50     override fun deserialize(decoder: Decoder): CheckedData<T> {
51         val inp = decoder.beginStructure(descriptor)
52         lateinit var data: T
53         lateinit var sum: ByteArray
54         loop@ while (true) {
55             when (val i = inp.decodeElementIndex(descriptor)) {
56                 CompositeDecoder.DECODE_DONE -> break@loop
57                 0 -> data = inp.decodeSerializableElement(descriptor, i, dataSerializer)
58                 1 -> sum = InternalHexConverter.parseHexBinary(inp.decodeStringElement(descriptor, i))
59                 else -> throw SerializationException("Unknown index $i")
60             }
61         }
62         inp.endStructure(descriptor)
63         return CheckedData(data, sum)
64     }
65 }
66 
67 @Serializable
68 data class DataWithString(@Serializable(with = CheckedDataSerializer::class) val data: CheckedData<String>)
69 
70 @Serializable
71 data class DataWithInt(@Serializable(with = CheckedDataSerializer::class) val data: CheckedData<Int>)
72 
73 @Serializable
74 data class DataWithStringContext(@Contextual val data: CheckedData<String>)
75 
76 
77 @Serializable
78 data class OptionalHolder(val optionalInt: Optional<Int>)
79 
80 @Serializable(OptionalSerializer::class)
81 sealed class Optional<out T : Any?> {
82     object NotPresent : Optional<Nothing>()
83     data class Value<T : Any?>(val value: T?) : Optional<T>()
84 
getnull85     fun get(): T? {
86         return when (this) {
87             NotPresent -> null
88             is Value -> this.value
89         }
90     }
91 }
92 
93 class OptionalSerializer<T>(
94     private val valueSerializer: KSerializer<T>
95 ) : KSerializer<Optional<T>> {
96     override val descriptor: SerialDescriptor = valueSerializer.descriptor
97 
deserializenull98     override fun deserialize(decoder: Decoder): Optional<T> {
99         return try {
100             Optional.Value(valueSerializer.deserialize(decoder))
101         } catch (exception: Exception) {
102             Optional.NotPresent
103         }
104     }
105 
serializenull106     override fun serialize(encoder: Encoder, value: Optional<T>) {
107         val msg = "Tried to serialize an optional property that had no value present. Is encodeDefaults false?"
108         when (value) {
109             Optional.NotPresent -> throw SerializationException(msg)
110             is Optional.Value ->
111                 when (val optional = value.value) {
112                     null -> encoder.encodeNull()
113                     else -> valueSerializer.serialize(encoder, optional)
114                 }
115         }
116     }
117 }
118 
119 
120 class GenericCustomSerializerTest {
121     @Test
testStringDatanull122     fun testStringData() {
123         val original = DataWithString(CheckedData("my data", byteArrayOf(42, 32)))
124         val s = Json.encodeToString(DataWithString.serializer(), original)
125         assertEquals("""{"data":{"data":"my data","checkSum":"2A20"}}""", s)
126         val restored = Json.decodeFromString(DataWithString.serializer(), s)
127         assertEquals(original, restored)
128     }
129 
130     @Test
testIntDatanull131     fun testIntData() {
132         val original = DataWithInt(CheckedData(42, byteArrayOf(42)))
133         val s = Json.encodeToString(DataWithInt.serializer(), original)
134         assertEquals("""{"data":{"data":42,"checkSum":"2A"}}""", s)
135         val restored = Json.decodeFromString(DataWithInt.serializer(), s)
136         assertEquals(original, restored)
137     }
138 
139 
140     @Test
testContextualGenericnull141     fun testContextualGeneric() {
142         val module = SerializersModule {
143             @Suppress("UNCHECKED_CAST")
144             contextual(CheckedData::class) { args -> CheckedDataSerializer(args[0] as KSerializer<Any>) }
145         }
146         assertStringFormAndRestored(
147             """{"data":{"data":"my data","checkSum":"2A20"}}""",
148             DataWithStringContext(CheckedData("my data", byteArrayOf(42, 32))),
149             DataWithStringContext.serializer(),
150             Json { serializersModule = module }
151         )
152     }
153 
154     @Test
testOnSealedClassnull155     fun testOnSealedClass() {
156         /*
157         Test on custom serializer for sealed class with generic parameter.
158         Related issues:
159              https://github.com/Kotlin/kotlinx.serialization/issues/1705
160              https://youtrack.jetbrains.com/issue/KT-50764
161              https://youtrack.jetbrains.com/issue/KT-50718
162              https://github.com/Kotlin/kotlinx.serialization/issues/1843
163          */
164         val encoded = Json.encodeToString(OptionalHolder(Optional.Value(42)))
165         assertEquals("""{"optionalInt":42}""", encoded)
166     }
167 }
168