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