1 /*
2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 @file:Suppress("FunctionName", "DeprecatedCallableAddReplaceWith")
6 
7 package kotlinx.serialization.properties
8 
9 import kotlinx.serialization.*
10 import kotlinx.serialization.descriptors.*
11 import kotlinx.serialization.encoding.*
12 import kotlinx.serialization.internal.*
13 import kotlinx.serialization.modules.*
14 
15 /**
16  * Transforms a [Serializable] class' properties into a single flat [Map] consisting of
17  * string keys and primitive type values, and vice versa.
18  *
19  * If the given class has non-primitive property `d` of arbitrary type `D`, `D` values are inserted
20  * into the same map; keys for such values are prefixed with string `d.`:
21  *
22  * ```
23  * @Serializable
24  * class Data(val property1: String)
25  *
26  * @Serializable
27  * class DataHolder(val data: Data, val property2: String)
28  *
29  * val map = Properties.store(DataHolder(Data("value1"), "value2"))
30  * // map contents will be the following:
31  * // property2 = value2
32  * // data.property1 = value1
33  * ```
34  *
35  * If the given class has a [List] property `l`, each value from the list
36  * would be prefixed with `l.N.`, where N is an index for a particular value.
37  * [Map] is treated as a `[key,value,...]` list.
38  *
39  * @param serializersModule A [SerializersModule] which should contain registered serializers
40  * for [Contextual] and [Polymorphic] serialization, if you have any.
41  */
42 @ExperimentalSerializationApi
43 @Suppress("UNUSED_PARAMETER")
44 public sealed class Properties(
45     override val serializersModule: SerializersModule,
46     ctorMarker: Nothing?
47 ) : SerialFormat {
48 
49     private abstract inner class OutMapper<Value : Any> : NamedValueEncoder() {
50         override val serializersModule: SerializersModule = this@Properties.serializersModule
51 
52         val map: MutableMap<String, Value> = mutableMapOf()
53 
encodenull54         protected abstract fun encode(value: Any): Value
55 
56         @Suppress("UNCHECKED_CAST")
57         final override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
58             if (serializer is AbstractPolymorphicSerializer<*>) {
59                 val casted = serializer as AbstractPolymorphicSerializer<Any>
60                 val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
61                 encodeTaggedString(nested("type"), actualSerializer.descriptor.serialName)
62 
63                 return actualSerializer.serialize(this, value)
64             }
65 
66             return serializer.serialize(this, value)
67         }
68 
encodeTaggedValuenull69         override fun encodeTaggedValue(tag: String, value: Any) {
70             map[tag] = encode(value)
71         }
72 
encodeTaggedNullnull73         override fun encodeTaggedNull(tag: String) {
74             // ignore nulls in output
75         }
76 
encodeTaggedEnumnull77         override fun encodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor, ordinal: Int) {
78             map[tag] = encode(enumDescriptor.getElementName(ordinal))
79         }
80     }
81 
82     private inner class OutAnyMapper : OutMapper<Any>() {
encodenull83         override fun encode(value: Any): Any = value
84     }
85 
86     private inner class OutStringMapper : OutMapper<String>() {
87         override fun encode(value: Any): String = value.toString()
88     }
89 
90     private abstract inner class InMapper<Value : Any>(
91         protected val map: Map<String, Value>, descriptor: SerialDescriptor
92     ) : NamedValueDecoder() {
93         override val serializersModule: SerializersModule = this@Properties.serializersModule
94 
95         private var currentIndex = 0
96         private val isCollection = descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP
97         private val size = if (isCollection) Int.MAX_VALUE else descriptor.elementsCount
98 
structurenull99         protected abstract fun structure(descriptor: SerialDescriptor): InMapper<Value>
100 
101         final override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
102             return structure(descriptor).also { copyTagsTo(it) }
103         }
104 
decodeSerializableValuenull105         final override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
106             if (deserializer is AbstractPolymorphicSerializer<*>) {
107                 val type = map[nested("type")]?.toString()
108                 val actualSerializer: DeserializationStrategy<Any> = deserializer.findPolymorphicSerializer(this, type)
109 
110                 @Suppress("UNCHECKED_CAST")
111                 return actualSerializer.deserialize(this) as T
112             }
113 
114             return deserializer.deserialize(this)
115         }
116 
decodeTaggedValuenull117         final override fun decodeTaggedValue(tag: String): Value {
118             return map.getValue(tag)
119         }
120 
decodeTaggedEnumnull121         final override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int {
122             return when (val taggedValue = map.getValue(tag)) {
123                 is Int -> taggedValue
124                 is String -> enumDescriptor.getElementIndex(taggedValue)
125                     .also { if (it == CompositeDecoder.UNKNOWN_NAME) throw SerializationException("Enum '${enumDescriptor.serialName}' does not contain element with name '$taggedValue'") }
126                 else -> throw SerializationException("Value of enum entry '$tag' is neither an Int nor a String")
127             }
128         }
129 
decodeElementIndexnull130         final override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
131             while (currentIndex < size) {
132                 val name = descriptor.getTag(currentIndex++)
133                 if (map.keys.any {
134                         it.startsWith(name) && (it.length == name.length || it[name.length] == '.')
135                     }) return currentIndex - 1
136                 if (isCollection) {
137                     // if map does not contain key we look for, then indices in collection have ended
138                     break
139                 }
140             }
141             return CompositeDecoder.DECODE_DONE
142         }
143     }
144 
145     private inner class InAnyMapper(
146         map: Map<String, Any>, descriptor: SerialDescriptor
147     ) : InMapper<Any>(map, descriptor) {
structurenull148         override fun structure(descriptor: SerialDescriptor): InAnyMapper =
149             InAnyMapper(map, descriptor)
150     }
151 
152     private inner class InStringMapper(
153         map: Map<String, String>, descriptor: SerialDescriptor
154     ) : InMapper<String>(map, descriptor) {
155         override fun structure(descriptor: SerialDescriptor): InStringMapper =
156             InStringMapper(map, descriptor)
157 
158         override fun decodeTaggedBoolean(tag: String): Boolean = decodeTaggedValue(tag).toBoolean()
159         override fun decodeTaggedByte(tag: String): Byte = decodeTaggedValue(tag).toByte()
160         override fun decodeTaggedShort(tag: String): Short = decodeTaggedValue(tag).toShort()
161         override fun decodeTaggedInt(tag: String): Int = decodeTaggedValue(tag).toInt()
162         override fun decodeTaggedLong(tag: String): Long = decodeTaggedValue(tag).toLong()
163         override fun decodeTaggedFloat(tag: String): Float = decodeTaggedValue(tag).toFloat()
164         override fun decodeTaggedDouble(tag: String): Double = decodeTaggedValue(tag).toDouble()
165         override fun decodeTaggedChar(tag: String): Char = decodeTaggedValue(tag).single()
166     }
167 
168     /**
169      * Encodes properties from the given [value] to a map using the given [serializer].
170      * `null` values are omitted from the output.
171      */
172     @ExperimentalSerializationApi
encodeToMapnull173     public fun <T> encodeToMap(serializer: SerializationStrategy<T>, value: T): Map<String, Any> {
174         val m = OutAnyMapper()
175         m.encodeSerializableValue(serializer, value)
176         return m.map
177     }
178 
179     /**
180      * Encodes properties from the given [value] to a map using the given [serializer].
181      * Converts all primitive types to [String] using [toString] method.
182      * `null` values are omitted from the output.
183      */
184     @ExperimentalSerializationApi
encodeToStringMapnull185     public fun <T> encodeToStringMap(serializer: SerializationStrategy<T>, value: T): Map<String, String> {
186         val m = OutStringMapper()
187         m.encodeSerializableValue(serializer, value)
188         return m.map
189     }
190 
191     /**
192      * Decodes properties from the given [map] to a value of type [T] using the given [deserializer].
193      * [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
194      */
195     @ExperimentalSerializationApi
decodeFromMapnull196     public fun <T> decodeFromMap(deserializer: DeserializationStrategy<T>, map: Map<String, Any>): T {
197         val m = InAnyMapper(map, deserializer.descriptor)
198         return m.decodeSerializableValue(deserializer)
199     }
200 
201     /**
202      * Decodes properties from the given [map] to a value of type [T] using the given [deserializer].
203      * [String] values are converted to respective primitive types using default conversion methods.
204      * [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
205      */
206     @ExperimentalSerializationApi
decodeFromStringMapnull207     public fun <T> decodeFromStringMap(deserializer: DeserializationStrategy<T>, map: Map<String, String>): T {
208         val m = InStringMapper(map, deserializer.descriptor)
209         return m.decodeSerializableValue(deserializer)
210     }
211 
212     /**
213      * A [Properties] instance that can be used as default and does not have any [SerializersModule] installed.
214      */
215     @ExperimentalSerializationApi
216     public companion object Default : Properties(EmptySerializersModule(), null)
217 }
218 
219 @OptIn(ExperimentalSerializationApi::class)
220 private class PropertiesImpl(serializersModule: SerializersModule) : Properties(serializersModule, null)
221 
222 /**
223  * Creates an instance of [Properties] with a given [module].
224  */
225 @ExperimentalSerializationApi
Propertiesnull226 public fun Properties(module: SerializersModule): Properties = PropertiesImpl(module)
227 
228 /**
229  * Encodes properties from given [value] to a map using serializer for reified type [T] and returns this map.
230  * `null` values are omitted from the output.
231  */
232 @ExperimentalSerializationApi
233 public inline fun <reified T> Properties.encodeToMap(value: T): Map<String, Any> =
234     encodeToMap(serializersModule.serializer(), value)
235 
236 /**
237  * Encodes properties from given [value] to a map using serializer for reified type [T] and returns this map.
238  * Converts all primitive types to [String] using [toString] method.
239  * `null` values are omitted from the output.
240  */
241 @ExperimentalSerializationApi
242 public inline fun <reified T> Properties.encodeToStringMap(value: T): Map<String, String> =
243     encodeToStringMap(serializersModule.serializer(), value)
244 
245 /**
246  * Decodes properties from given [map], assigns them to an object using serializer for reified type [T] and returns this object.
247  * [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
248  */
249 @ExperimentalSerializationApi
250 public inline fun <reified T> Properties.decodeFromMap(map: Map<String, Any>): T =
251     decodeFromMap(serializersModule.serializer(), map)
252 
253 /**
254  * Decodes properties from given [map], assigns them to an object using serializer for reified type [T] and returns this object.
255  * [String] values are converted to respective primitive types using default conversion methods.
256  * [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
257  */
258 @ExperimentalSerializationApi
259 public inline fun <reified T> Properties.decodeFromStringMap(map: Map<String, String>): T =
260     decodeFromStringMap(serializersModule.serializer(), map)
261 
262 // Migrations below
263 
264 @PublishedApi
265 internal fun noImpl(): Nothing = throw UnsupportedOperationException("Not implemented, should not be called")
266