Skip to content

Commit 816bd62

Browse files
committed
Adding Serialization fixes
1 parent eb56044 commit 816bd62

File tree

58 files changed

+1980
-1100
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1980
-1100
lines changed

firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt

+31-27
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,41 @@
44

55
package dev.gitlive.firebase
66

7-
import kotlinx.serialization.encoding.CompositeDecoder
87
import kotlinx.serialization.descriptors.PolymorphicKind
98
import kotlinx.serialization.descriptors.SerialDescriptor
109
import kotlinx.serialization.descriptors.StructureKind
10+
import kotlinx.serialization.encoding.CompositeDecoder
1111

12-
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind) {
13-
StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map ->
14-
FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index ->
15-
val elementName = desc.getElementName(index)
16-
if (desc.kind is PolymorphicKind && elementName == "value") {
17-
map
18-
} else {
19-
map[desc.getElementName(index)]
20-
}
21-
}
22-
}
23-
StructureKind.LIST ->
24-
when(value) {
25-
is List<*> -> value
26-
is Map<*, *> -> value.asSequence()
27-
.sortedBy { (it) -> it.toString().toIntOrNull() }
28-
.map { (_, it) -> it }
29-
.toList()
30-
else -> error("unexpected type, got $value when expecting a list")
31-
}
32-
.let { FirebaseCompositeDecoder(it.size) { _, index -> it[index] } }
33-
StructureKind.MAP -> (value as Map<*, *>).entries.toList().let {
34-
FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
35-
}
36-
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
12+
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) {
13+
StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false)
14+
StructureKind.LIST -> (value as? List<*>).orEmpty().let {
15+
FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] }
3716
}
3817

18+
StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let {
19+
FirebaseCompositeDecoder(
20+
it.size,
21+
settings
22+
) { _, index -> it[index / 2].run { if (index % 2 == 0) key else value } }
23+
}
24+
25+
is PolymorphicKind -> decodeAsMap(polymorphicIsNested)
26+
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
27+
}
28+
3929
actual fun getPolymorphicType(value: Any?, discriminator: String): String =
40-
(value as Map<*,*>)[discriminator] as String
30+
(value as? Map<*,*>).orEmpty()[discriminator] as String
31+
32+
private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map ->
33+
FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index ->
34+
if (isNestedPolymorphic) {
35+
if (index == 0)
36+
map[desc.getElementName(index)]
37+
else {
38+
map
39+
}
40+
} else {
41+
map[desc.getElementName(index)]
42+
}
43+
}
44+
}

firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt

+15-8
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,30 @@
44

55
package dev.gitlive.firebase
66

7+
import kotlinx.serialization.descriptors.PolymorphicKind
78
import kotlinx.serialization.descriptors.SerialDescriptor
89
import kotlinx.serialization.descriptors.StructureKind
910
import kotlin.collections.set
1011

1112
actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) {
1213
StructureKind.LIST -> mutableListOf<Any?>()
1314
.also { value = it }
14-
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> it.add(index, value) } }
15+
.let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } }
1516
StructureKind.MAP -> mutableListOf<Any?>()
16-
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } }
17-
StructureKind.CLASS, StructureKind.OBJECT -> mutableMapOf<Any?, Any?>()
18-
.also { value = it }
19-
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault,
17+
.let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } }
18+
StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor)
19+
is PolymorphicKind -> encodeAsMap(descriptor)
20+
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
21+
}
22+
23+
private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf<Any?, Any?>()
24+
.also { value = it }
25+
.let {
26+
FirebaseCompositeEncoder(
27+
settings,
2028
setPolymorphicType = { discriminator, type ->
2129
it[discriminator] = type
2230
},
2331
set = { _, index, value -> it[descriptor.getElementName(index)] = value }
24-
) }
25-
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
26-
}
32+
)
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dev.gitlive.firebase
2+
3+
import kotlinx.serialization.modules.EmptySerializersModule
4+
import kotlinx.serialization.modules.SerializersModule
5+
6+
/**
7+
* Settings used to configure encoding/decoding
8+
*/
9+
sealed class EncodeDecodeSettings {
10+
11+
/**
12+
* The [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime
13+
*/
14+
abstract val serializersModule: SerializersModule
15+
}
16+
17+
/**
18+
* [EncodeDecodeSettings] used when encoding an object
19+
* @property shouldEncodeElementDefault if `true` this will explicitly encode elements even if they are their default value
20+
* @param serializersModule the [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime
21+
*/
22+
data class EncodeSettings(
23+
val shouldEncodeElementDefault: Boolean = true,
24+
override val serializersModule: SerializersModule = EmptySerializersModule(),
25+
) : EncodeDecodeSettings()
26+
27+
/**
28+
* [EncodeDecodeSettings] used when decoding an object
29+
* @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime
30+
*/
31+
data class DecodeSettings(
32+
override val serializersModule: SerializersModule = EmptySerializersModule(),
33+
) : EncodeDecodeSettings()

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ internal fun <T> FirebaseEncoder.encodePolymorphically(
1616
value: T,
1717
ifPolymorphic: (String) -> Unit
1818
) {
19+
// If serializer is not an AbstractPolymorphicSerializer or if we are encoding this as a list, we can just use the regular serializer
20+
// This will result in calling structureEncoder for complicated structures
21+
// For PolymorphicKind this will first encode the polymorphic discriminator as a String and the remaining StructureKind.Class as a map of key-value pairs
22+
// This will result in a list structured like: (type, { classKey = classValue })
1923
if (serializer !is AbstractPolymorphicSerializer<*>) {
2024
serializer.serialize(this, value)
2125
return
2226
}
27+
28+
// When doing Polymorphic Serialization with EncodeDecodeSettings.PolymorphicStructure.MAP we will use the polymorphic serializer of the class.
2329
val casted = serializer as AbstractPolymorphicSerializer<Any>
2430
val baseClassDiscriminator = serializer.descriptor.classDiscriminator()
2531
val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
@@ -32,15 +38,15 @@ internal fun <T> FirebaseDecoder.decodeSerializableValuePolymorphic(
3238
value: Any?,
3339
deserializer: DeserializationStrategy<T>,
3440
): T {
41+
// If deserializer is not an AbstractPolymorphicSerializer or if we are decoding this from a list, we can just use the regular serializer
3542
if (deserializer !is AbstractPolymorphicSerializer<*>) {
3643
return deserializer.deserialize(this)
3744
}
38-
3945
val casted = deserializer as AbstractPolymorphicSerializer<Any>
4046
val discriminator = deserializer.descriptor.classDiscriminator()
4147
val type = getPolymorphicType(value, discriminator)
4248
val actualDeserializer = casted.findPolymorphicSerializerOrNull(
43-
structureDecoder(deserializer.descriptor),
49+
structureDecoder(deserializer.descriptor, false),
4450
type
4551
) as DeserializationStrategy<T>
4652
return actualDeserializer.deserialize(this)
@@ -55,4 +61,3 @@ internal fun SerialDescriptor.classDiscriminator(): String {
5561
}
5662
return "type"
5763
}
58-

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt

+68-33
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,26 @@ import kotlinx.serialization.modules.EmptySerializersModule
1515
import kotlinx.serialization.modules.SerializersModule
1616
import kotlinx.serialization.serializer
1717

18-
@Suppress("UNCHECKED_CAST")
19-
inline fun <reified T> decode(value: Any?): T {
18+
inline fun <reified T> decode(value: Any?): T = decode(value, DecodeSettings())
19+
inline fun <reified T> decode(value: Any?, settings: DecodeSettings): T {
2020
val strategy = serializer<T>()
21-
return decode(strategy as DeserializationStrategy<T>, value)
21+
return decode(strategy as DeserializationStrategy<T>, value, settings)
2222
}
23-
24-
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T {
23+
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T = decode(strategy, value, DecodeSettings())
24+
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, settings: DecodeSettings): T {
2525
require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" }
26-
return FirebaseDecoder(value).decodeSerializableValue(strategy)
26+
return FirebaseDecoder(value, settings).decodeSerializableValue(strategy)
2727
}
28-
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder
28+
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder
2929
expect fun getPolymorphicType(value: Any?, discriminator: String): String
3030

31-
class FirebaseDecoder(internal val value: Any?) : Decoder {
31+
class FirebaseDecoder(val value: Any?, internal val settings: DecodeSettings) : Decoder {
32+
33+
constructor(value: Any?) : this(value, DecodeSettings())
3234

33-
override val serializersModule: SerializersModule
34-
get() = EmptySerializersModule()
35+
override val serializersModule: SerializersModule = settings.serializersModule
3536

36-
override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor)
37+
override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor, true)
3738

3839
override fun decodeString() = decodeString(value)
3940

@@ -59,7 +60,7 @@ class FirebaseDecoder(internal val value: Any?) : Decoder {
5960

6061
override fun decodeNull() = decodeNull(value)
6162

62-
override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value)
63+
override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value, settings)
6364

6465
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
6566
return decodeSerializableValuePolymorphic(value, deserializer)
@@ -68,26 +69,35 @@ class FirebaseDecoder(internal val value: Any?) : Decoder {
6869

6970
class FirebaseClassDecoder(
7071
size: Int,
72+
settings: DecodeSettings,
7173
private val containsKey: (name: String) -> Boolean,
7274
get: (descriptor: SerialDescriptor, index: Int) -> Any?
73-
) : FirebaseCompositeDecoder(size, get) {
75+
) : FirebaseCompositeDecoder(size, settings, get) {
7476
private var index: Int = 0
7577

7678
override fun decodeSequentially() = false
7779

78-
override fun decodeElementIndex(descriptor: SerialDescriptor): Int =
79-
(index until descriptor.elementsCount)
80-
.firstOrNull { !descriptor.isElementOptional(it) || containsKey(descriptor.getElementName(it)) }
80+
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
81+
return (index until descriptor.elementsCount)
82+
.firstOrNull {
83+
!descriptor.isElementOptional(it) || containsKey(
84+
descriptor.getElementName(
85+
it
86+
)
87+
)
88+
}
8189
?.also { index = it + 1 }
8290
?: DECODE_DONE
91+
}
8392
}
8493

8594
open class FirebaseCompositeDecoder(
8695
private val size: Int,
87-
private val get: (descriptor: SerialDescriptor, index: Int) -> Any?
96+
internal val settings: DecodeSettings,
97+
private val get: (descriptor: SerialDescriptor, index: Int) -> Any?,
8898
): CompositeDecoder {
8999

90-
override val serializersModule = EmptySerializersModule()
100+
override val serializersModule: SerializersModule = settings.serializersModule
91101

92102
override fun decodeSequentially() = true
93103

@@ -100,21 +110,30 @@ open class FirebaseCompositeDecoder(
100110
index: Int,
101111
deserializer: DeserializationStrategy<T>,
102112
previousValue: T?
103-
) = deserializer.deserialize(FirebaseDecoder(get(descriptor, index)))
113+
) = decodeElement(descriptor, index) {
114+
deserializer.deserialize(FirebaseDecoder(it, settings))
115+
}
104116

105-
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = decodeBoolean(get(descriptor, index))
117+
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) =
118+
decodeElement(descriptor, index, ::decodeBoolean)
106119

107-
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = decodeByte(get(descriptor, index))
120+
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) =
121+
decodeElement(descriptor, index, ::decodeByte)
108122

109-
override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = decodeChar(get(descriptor, index))
123+
override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) =
124+
decodeElement(descriptor, index, ::decodeChar)
110125

111-
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble(get(descriptor, index))
126+
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) =
127+
decodeElement(descriptor, index, ::decodeDouble)
112128

113-
override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = decodeFloat(get(descriptor, index))
129+
override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) =
130+
decodeElement(descriptor, index, ::decodeFloat)
114131

115-
override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = decodeInt(get(descriptor, index))
132+
override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) =
133+
decodeElement(descriptor, index, ::decodeInt)
116134

117-
override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = decodeLong(get(descriptor, index))
135+
override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) =
136+
decodeElement(descriptor, index, ::decodeLong)
118137

119138
override fun <T : Any> decodeNullableSerializableElement(
120139
descriptor: SerialDescriptor,
@@ -123,19 +142,37 @@ open class FirebaseCompositeDecoder(
123142
previousValue: T?
124143
): T? {
125144
val isNullabilitySupported = deserializer.descriptor.isNullable
126-
return if (isNullabilitySupported || decodeNotNullMark(get(descriptor, index))) decodeSerializableElement(descriptor, index, deserializer, previousValue) else decodeNull(get(descriptor, index))
145+
return if (isNullabilitySupported || decodeElement(descriptor, index, ::decodeNotNullMark)) {
146+
decodeSerializableElement(descriptor, index, deserializer, previousValue)
147+
} else {
148+
decodeElement(descriptor, index, ::decodeNull)
149+
}
127150
}
128151

129-
override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = decodeShort(get(descriptor, index))
152+
override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) =
153+
decodeElement(descriptor, index, ::decodeShort)
130154

131-
override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = decodeString(get(descriptor, index))
155+
override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) =
156+
decodeElement(descriptor, index, ::decodeString)
132157

133158
override fun endStructure(descriptor: SerialDescriptor) {}
134159

135160
@ExperimentalSerializationApi
136161
override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder =
137-
FirebaseDecoder(get(descriptor, index))
138-
162+
decodeElement(descriptor, index) {
163+
FirebaseDecoder(it, settings)
164+
}
165+
166+
private fun <T> decodeElement(descriptor: SerialDescriptor, index: Int, decoder: (Any?) -> T): T {
167+
return try {
168+
decoder(get(descriptor, index))
169+
} catch (e: Exception) {
170+
throw SerializationException(
171+
message = "Exception during decoding ${descriptor.serialName} ${descriptor.getElementName(index)}",
172+
cause = e
173+
)
174+
}
175+
}
139176
}
140177

141178
private fun decodeString(value: Any?) = value.toString()
@@ -201,5 +238,3 @@ internal fun SerialDescriptor.getElementIndexOrThrow(name: String): Int {
201238
private fun decodeNotNullMark(value: Any?) = value != null
202239

203240
private fun decodeNull(value: Any?) = value as Nothing?
204-
205-

0 commit comments

Comments
 (0)