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