Skip to content

Commit 71bcb81

Browse files
authored
Merge pull request #448 from splendo/feature/serialization-fixes
Adding Serialization fixes
2 parents d13f931 + f8eb4fd commit 71bcb81

File tree

62 files changed

+2649
-1394
lines changed

Some content is hidden

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

62 files changed

+2649
-1394
lines changed

README.md

+33-3
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,43 @@ data class City(val name: String)
8585
Instances of these classes can now be passed [along with their serializer](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#introduction-to-serializers) to the SDK:
8686

8787
```kotlin
88-
db.collection("cities").document("LA").set(City.serializer(), city, encodeDefaults = true)
88+
db.collection("cities").document("LA").set(City.serializer(), city) { encodeDefaults = true }
8989
```
9090

91-
The `encodeDefaults` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values.
91+
The `buildSettings` closure is optional and allows for configuring serialization behaviour.
92+
93+
Setting the `encodeDefaults` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values.
9294
Using [@EncodeDefault](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) on properties is a recommended way to locally override the behavior set with `encodeDefaults`.
9395

94-
You can also omit the serializer but this is discouraged due to a [current limitation on Kotlin/JS and Kotlin/Native](https://github.com/Kotlin/kotlinx.serialization/issues/1116#issuecomment-704342452)
96+
You can also omit the serializer if it can be inferred using `serializer<KType>()`.
97+
To support [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization) or [open polymorphism](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) the `serializersModule` can be overridden in the `buildSettings` closure:
98+
99+
```kotlin
100+
@Serializable
101+
abstract class AbstractCity {
102+
abstract val name: String
103+
}
104+
105+
@Serializable
106+
@SerialName("capital")
107+
data class Capital(override val name: String, val isSeatOfGovernment: Boolean) : AbstractCity()
108+
109+
val module = SerializersModule {
110+
polymorphic(AbstractCity::class, AbstractCity.serializer()) {
111+
subclass(Capital::class, Capital.serializer())
112+
}
113+
}
114+
115+
val city = Capital("London", true)
116+
db.collection("cities").document("UK").set(AbstractCity.serializer(), city) {
117+
encodeDefaults = true
118+
serializersModule = module
119+
120+
}
121+
val storedCity = db.collection("cities").document("UK").get().data(AbstractCity.serializer()) {
122+
serializersModule = module
123+
}
124+
```
95125

96126
<h4><a href="https://firebase.google.com/docs/firestore/manage-data/add-data#server_timestamp">Server Timestamp</a></h3>
97127

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 (desc.getElementName(index) == "value")
36+
map
37+
else {
38+
map[desc.getElementName(index)]
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,70 @@
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 encodeDefaults 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 internal constructor(
23+
val encodeDefaults: Boolean,
24+
override val serializersModule: SerializersModule,
25+
) : EncodeDecodeSettings() {
26+
27+
interface Builder {
28+
var encodeDefaults: Boolean
29+
var serializersModule: SerializersModule
30+
31+
}
32+
33+
@PublishedApi
34+
internal class BuilderImpl : Builder {
35+
override var encodeDefaults: Boolean = true
36+
override var serializersModule: SerializersModule = EmptySerializersModule()
37+
}
38+
}
39+
40+
/**
41+
* [EncodeDecodeSettings] used when decoding an object
42+
* @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime
43+
*/
44+
data class DecodeSettings internal constructor(
45+
override val serializersModule: SerializersModule = EmptySerializersModule(),
46+
) : EncodeDecodeSettings() {
47+
48+
interface Builder {
49+
var serializersModule: SerializersModule
50+
}
51+
52+
@PublishedApi
53+
internal class BuilderImpl : Builder {
54+
override var serializersModule: SerializersModule = EmptySerializersModule()
55+
}
56+
}
57+
58+
interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.Builder
59+
60+
@PublishedApi
61+
internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder {
62+
63+
override var encodeDefaults: Boolean = true
64+
override var serializersModule: SerializersModule = EmptySerializersModule()
65+
}
66+
67+
@PublishedApi
68+
internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(encodeDefaults, serializersModule)
69+
@PublishedApi
70+
internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule)

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ internal fun <T> FirebaseEncoder.encodePolymorphically(
1616
value: T,
1717
ifPolymorphic: (String) -> Unit
1818
) {
19+
// If serializer is not an AbstractPolymorphicSerializer 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+
2328
val casted = serializer as AbstractPolymorphicSerializer<Any>
2429
val baseClassDiscriminator = serializer.descriptor.classDiscriminator()
2530
val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
@@ -32,15 +37,15 @@ internal fun <T> FirebaseDecoder.decodeSerializableValuePolymorphic(
3237
value: Any?,
3338
deserializer: DeserializationStrategy<T>,
3439
): T {
40+
// If deserializer is not an AbstractPolymorphicSerializer we can just use the regular serializer
3541
if (deserializer !is AbstractPolymorphicSerializer<*>) {
3642
return deserializer.deserialize(this)
3743
}
38-
3944
val casted = deserializer as AbstractPolymorphicSerializer<Any>
4045
val discriminator = deserializer.descriptor.classDiscriminator()
4146
val type = getPolymorphicType(value, discriminator)
4247
val actualDeserializer = casted.findPolymorphicSerializerOrNull(
43-
structureDecoder(deserializer.descriptor),
48+
structureDecoder(deserializer.descriptor, false),
4449
type
4550
) as DeserializationStrategy<T>
4651
return actualDeserializer.deserialize(this)
@@ -55,4 +60,3 @@ internal fun SerialDescriptor.classDiscriminator(): String {
5560
}
5661
return "type"
5762
}
58-

0 commit comments

Comments
 (0)