1 /* 2 * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.serialization.json 6 7 import kotlinx.serialization.* 8 import kotlinx.serialization.descriptors.* 9 import kotlinx.serialization.encoding.* 10 import kotlinx.serialization.modules.* 11 import kotlin.reflect.* 12 13 /** 14 * Base class for custom serializers that allows selecting polymorphic serializer 15 * without a dedicated class discriminator, on a content basis. 16 * 17 * Usually, polymorphic serialization (represented by [PolymorphicSerializer] and [SealedClassSerializer]) 18 * requires a dedicated `"type"` property in the JSON to 19 * determine actual serializer that is used to deserialize Kotlin class. 20 * 21 * However, sometimes (e.g. when interacting with external API) type property is not present in the input 22 * and it is expected to guess the actual type by the shape of JSON, for example by the presence of specific key. 23 * [JsonContentPolymorphicSerializer] provides a skeleton implementation for such strategy. Please note that 24 * since JSON content is represented by [JsonElement] class and could be read only with [JsonDecoder] decoder, 25 * this class works only with [Json] format. 26 * 27 * Deserialization happens in two stages: first, a value from the input JSON is read 28 * to as a [JsonElement]. Second, [selectDeserializer] function is called to determine which serializer should be used. 29 * The returned serializer is used to deserialize [JsonElement] back to Kotlin object. 30 * 31 * It is possible to serialize values this serializer. In that case, class discriminator property won't 32 * be added to JSON stream, i.e., deserializing a class from the string and serializing it back yields the original string. 33 * However, to determine a serializer, a standard polymorphic mechanism represented by [SerializersModule] is used. 34 * For convenience, [serialize] method can lookup default serializer, but it is recommended to follow 35 * standard procedure with [registering][SerializersModuleBuilder.polymorphic]. 36 * 37 * Usage example: 38 * ``` 39 * interface Payment { 40 * val amount: String 41 * } 42 * 43 * @Serializable 44 * data class SuccessfulPayment(override val amount: String, val date: String) : Payment 45 * 46 * @Serializable 47 * data class RefundedPayment(override val amount: String, val date: String, val reason: String) : Payment 48 * 49 * object PaymentSerializer : JsonContentPolymorphicSerializer<Payment>(Payment::class) { 50 * override fun selectDeserializer(content: JsonElement) = when { 51 * "reason" in content.jsonObject -> RefundedPayment.serializer() 52 * else -> SuccessfulPayment.serializer() 53 * } 54 * } 55 * 56 * // Now both statements will yield different subclasses of Payment: 57 * 58 * Json.decodeFromString(PaymentSerializer, """{"amount":"1.0","date":"03.02.2020"}""") 59 * Json.decodeFromString(PaymentSerializer, """{"amount":"2.0","date":"03.02.2020","reason":"complaint"}""") 60 * ``` 61 * 62 * @param T A root type for all classes that could be possibly encountered during serialization and deserialization. 63 * Must be non-final class or interface. 64 * @param baseClass A class token for [T]. 65 */ 66 @OptIn(ExperimentalSerializationApi::class) 67 public abstract class JsonContentPolymorphicSerializer<T : Any>(private val baseClass: KClass<T>) : KSerializer<T> { 68 /** 69 * A descriptor for this set of content-based serializers. 70 * By default, it uses the name composed of [baseClass] simple name, 71 * kind is set to [PolymorphicKind.SEALED] and contains 0 elements. 72 * 73 * However, this descriptor can be overridden to achieve better representation of custom transformed JSON shape 74 * for schema generating/introspection purposes. 75 */ 76 override val descriptor: SerialDescriptor = 77 buildSerialDescriptor("JsonContentPolymorphicSerializer<${baseClass.simpleName}>", PolymorphicKind.SEALED) 78 serializenull79 final override fun serialize(encoder: Encoder, value: T) { 80 val actualSerializer = 81 encoder.serializersModule.getPolymorphic(baseClass, value) 82 ?: value::class.serializerOrNull() 83 ?: throwSubtypeNotRegistered(value::class, baseClass) 84 @Suppress("UNCHECKED_CAST") 85 (actualSerializer as KSerializer<T>).serialize(encoder, value) 86 } 87 deserializenull88 final override fun deserialize(decoder: Decoder): T { 89 val input = decoder.asJsonDecoder() 90 val tree = input.decodeJsonElement() 91 92 @Suppress("UNCHECKED_CAST") 93 val actualSerializer = selectDeserializer(tree) as KSerializer<T> 94 return input.json.decodeFromJsonElement(actualSerializer, tree) 95 } 96 97 /** 98 * Determines a particular strategy for deserialization by looking on a parsed JSON [element]. 99 */ selectDeserializernull100 protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T> 101 102 private fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing { 103 val subClassName = subClass.simpleName ?: "$subClass" 104 val scope = "in the scope of '${baseClass.simpleName}'" 105 throw SerializationException( 106 "Class '${subClassName}' is not registered for polymorphic serialization $scope.\n" + 107 "Mark the base class as 'sealed' or register the serializer explicitly.") 108 } 109 110 } 111