diff --git a/README.md b/README.md index 26101e39d..084fbb39d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,33 @@ data class Post( ``` +

Polymorphic serialization (sealed classes)

+ +This sdk will handle polymorphic serialization automatically if you have a sealed class and its children marked as `Serializable`. It will include a `type` property that will be used to discriminate which child class is the serialized. + +You can change this `type` property by using the `@FirebaseClassDiscrminator` annotation in the parent sealed class: + +```kotlin +@Serializable +@FirebaseClassDiscriminator("class") +sealed class Parent { + @Serializable + @SerialName("child") + data class Child( + val property: Boolean + ) : Parent +} +``` + +In combination with a `SerialName` specified for the child class, you have full control over the serialized data. In this case it will be: + +```json +{ + "class": "child", + "property": true +} +``` +

Default arguments

To reduce boilerplate, default arguments are used in the places where the Firebase Android SDK employs the builder pattern: diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt index 2b31cece7..4516eabbe 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -5,13 +5,12 @@ package dev.gitlive.firebase import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) { - StructureKind.CLASS, StructureKind.OBJECT -> (value as Map<*, *>).let { map -> +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map -> FirebaseClassDecoder(decodeDouble, map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] } } StructureKind.LIST -> (value as List<*>).let { @@ -20,4 +19,8 @@ actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decode StructureKind.MAP -> (value as Map<*, *>).entries.toList().let { FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } } - } \ No newline at end of file + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") + } + +actual fun getPolymorphicType(value: Any?, discriminator: String): String = + (value as Map<*,*>)[discriminator] as String \ No newline at end of file diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index 84576846e..928936d38 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -4,12 +4,11 @@ package dev.gitlive.firebase -import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): CompositeEncoder = when(descriptor.kind as StructureKind) { +actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it.add(index, value) } } @@ -17,5 +16,11 @@ actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): Compo .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } StructureKind.CLASS, StructureKind.OBJECT -> mutableMapOf() .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it[descriptor.getElementName(index)] = value } } + .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity, + setPolymorphicType = { discriminator, type -> + it[discriminator] = type + }, + set = { _, index, value -> it[descriptor.getElementName(index)] = value } + ) } + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } \ No newline at end of file diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt new file mode 100644 index 000000000..2ed4bafa1 --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.InheritableSerialInfo + +@InheritableSerialInfo +@Target(AnnotationTarget.CLASS) +annotation class FirebaseClassDiscriminator(val discriminator: String) \ No newline at end of file diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt new file mode 100644 index 000000000..aceb6002f --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt @@ -0,0 +1,59 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.findPolymorphicSerializer +import kotlinx.serialization.internal.AbstractPolymorphicSerializer + +/* + * This code was inspired on polymorphic json serialization of kotlinx.serialization. + * See https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt + */ +@Suppress("UNCHECKED_CAST") +internal fun FirebaseEncoder.encodePolymorphically( + serializer: SerializationStrategy, + value: T, + ifPolymorphic: (String) -> Unit +) { + if (serializer !is AbstractPolymorphicSerializer<*>) { + serializer.serialize(this, value) + return + } + val casted = serializer as AbstractPolymorphicSerializer + val baseClassDiscriminator = serializer.descriptor.classDiscriminator() + val actualSerializer = casted.findPolymorphicSerializer(this, value as Any) + ifPolymorphic(baseClassDiscriminator) + actualSerializer.serialize(this, value) +} + +@Suppress("UNCHECKED_CAST") +internal fun FirebaseDecoder.decodeSerializableValuePolymorphic( + value: Any?, + decodeDouble: (value: Any?) -> Double?, + deserializer: DeserializationStrategy, +): T { + if (deserializer !is AbstractPolymorphicSerializer<*>) { + return deserializer.deserialize(this) + } + + val casted = deserializer as AbstractPolymorphicSerializer + val discriminator = deserializer.descriptor.classDiscriminator() + val type = getPolymorphicType(value, discriminator) + val actualDeserializer = casted.findPolymorphicSerializerOrNull( + structureDecoder(deserializer.descriptor, decodeDouble), + type + ) as DeserializationStrategy + return actualDeserializer.deserialize(this) +} + +internal fun SerialDescriptor.classDiscriminator(): String { + // Plain loop is faster than allocation of Sequence or ArrayList + // We can rely on the fact that only one FirebaseClassDiscriminator is present — + // compiler plugin checked that. + for (annotation in annotations) { + if (annotation is FirebaseClassDiscriminator) return annotation.discriminator + } + return "type" +} + diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index c771d63e1..6be266b56 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -25,8 +25,8 @@ fun decode(strategy: DeserializationStrategy, value: Any?, decodeDouble: require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } return FirebaseDecoder(value, decodeDouble).decodeSerializableValue(strategy) } - expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder +expect fun getPolymorphicType(value: Any?, discriminator: String): String class FirebaseDecoder(internal val value: Any?, private val decodeDouble: (value: Any?) -> Double?) : Decoder { @@ -59,8 +59,11 @@ class FirebaseDecoder(internal val value: Any?, private val decodeDouble: (value override fun decodeNull() = decodeNull(value) - @ExperimentalSerializationApi override fun decodeInline(inlineDescriptor: SerialDescriptor) = FirebaseDecoder(value, decodeDouble) + + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { + return decodeSerializableValuePolymorphic(value, decodeDouble, deserializer) + } } class FirebaseClassDecoder( @@ -80,7 +83,7 @@ class FirebaseClassDecoder( ?: DECODE_DONE } -open class FirebaseCompositeDecoder constructor( +open class FirebaseCompositeDecoder( private val decodeDouble: (value: Any?) -> Double?, private val size: Int, private val get: (descriptor: SerialDescriptor, index: Int) -> Any? @@ -134,6 +137,7 @@ open class FirebaseCompositeDecoder constructor( @ExperimentalSerializationApi override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder = FirebaseDecoder(get(descriptor, index), decodeDouble) + } private fun decodeString(value: Any?) = value.toString() diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index afb637492..7afe032bc 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -16,14 +16,23 @@ inline fun encode(value: T, shouldEncodeElementDefault: Boolean, pos FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity).apply { encodeSerializableValue(it.firebaseSerializer(), it) }.value } -expect fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): CompositeEncoder +expect fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean, positiveInfinity: Any) : TimestampEncoder(positiveInfinity), Encoder { var value: Any? = null override val serializersModule = EmptySerializersModule - override fun beginStructure(descriptor: SerialDescriptor) = structureEncoder(descriptor) + private var polymorphicDiscriminator: String? = null + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + val encoder = structureEncoder(descriptor) + if (polymorphicDiscriminator != null) { + encoder.encodePolymorphicClassDiscriminator(polymorphicDiscriminator!!, descriptor.serialName) + polymorphicDiscriminator = null + } + return encoder + } override fun encodeBoolean(value: Boolean) { this.value = value @@ -73,9 +82,14 @@ class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean, positive this.value = value } - @ExperimentalSerializationApi override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder = FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity) + + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + encodePolymorphically(serializer, value) { + polymorphicDiscriminator = it + } + } } abstract class TimestampEncoder(internal val positiveInfinity: Any) { @@ -89,7 +103,8 @@ open class FirebaseCompositeEncoder constructor( private val shouldEncodeElementDefault: Boolean, positiveInfinity: Any, private val end: () -> Unit = {}, - private val set: (descriptor: SerialDescriptor, index: Int, value: Any?) -> Unit + private val setPolymorphicType: (String, String) -> Unit = { _, _ -> }, + private val set: (descriptor: SerialDescriptor, index: Int, value: Any?) -> Unit, ): TimestampEncoder(positiveInfinity), CompositeEncoder { override val serializersModule = EmptySerializersModule @@ -153,6 +168,9 @@ open class FirebaseCompositeEncoder constructor( @ExperimentalSerializationApi override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder = FirebaseEncoder(shouldEncodeElementDefault, positiveInfinity) -} + fun encodePolymorphicClassDiscriminator(discriminator: String, type: String) { + setPolymorphicType(discriminator, type) + } +} diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 2d5741835..d61c1e5d1 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -4,6 +4,7 @@ package dev.gitlive.firebase +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlin.test.Test @@ -17,6 +18,13 @@ expect fun nativeAssertEquals(expected: Any?, actual: Any?): Unit @Serializable data class TestData(val map: Map, val bool: Boolean = false, val nullableBool: Boolean? = null) +@Serializable +sealed class TestSealed { + @Serializable + @SerialName("child") + data class ChildClass(val map: Map, val bool: Boolean = false): TestSealed() +} + class EncodersTest { @Test fun encodeMap() { @@ -37,6 +45,12 @@ class EncodersTest { nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "bool" to true, "nullableBool" to true), encoded) } + @Test + fun encodeSealedClass() { + val encoded = encode(TestSealed.serializer(), TestSealed.ChildClass(mapOf("key" to "value"), true), shouldEncodeElementDefault = true) + nativeAssertEquals(nativeMapOf("type" to "child", "map" to nativeMapOf("key" to "value"), "bool" to true), encoded) + } + @Test fun decodeObject() { val decoded = decode(TestData.serializer(), nativeMapOf("map" to nativeMapOf("key" to "value"))) @@ -54,4 +68,10 @@ class EncodersTest { val decoded = decode(TestData.serializer(), nativeMapOf("map" to mapOf("key" to "value"), "nullableBool" to null)) assertNull(decoded.nullableBool) } + + @Test + fun decodeSealedClass() { + val decoded = decode(TestSealed.serializer(), nativeMapOf("type" to "child", "map" to nativeMapOf("key" to "value"), "bool" to true)) + assertEquals(TestSealed.ChildClass(mapOf("key" to "value"), true), decoded) + } } \ No newline at end of file diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt index 05ffad2cd..5e87c2f9a 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -5,13 +5,12 @@ package dev.gitlive.firebase import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) { - StructureKind.CLASS, StructureKind.OBJECT -> (value as Map<*, *>).let { map -> +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map -> FirebaseClassDecoder(decodeDouble, map.size, { map.containsKey(it) }) { desc, index -> map[desc.getElementName(index)] } } StructureKind.LIST -> (value as List<*>).let { @@ -20,4 +19,8 @@ actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decode StructureKind.MAP -> (value as Map<*, *>).entries.toList().let { FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } } -} \ No newline at end of file + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +} + +actual fun getPolymorphicType(value: Any?, discriminator: String): String = + (value as Map<*,*>)[discriminator] as String \ No newline at end of file diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 84576846e..e887f1957 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -4,18 +4,25 @@ package dev.gitlive.firebase +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): CompositeEncoder = when(descriptor.kind as StructureKind) { +actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it.add(index, value) } } StructureKind.MAP -> mutableListOf() .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } - StructureKind.CLASS, StructureKind.OBJECT -> mutableMapOf() + StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> mutableMapOf() .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it[descriptor.getElementName(index)] = value } } + .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity, + setPolymorphicType = { discriminator, type -> + it[discriminator] = type + }, + set = { _, index, value -> it[descriptor.getElementName(index)] = value } + ) } + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } \ No newline at end of file diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt index 4e0d0b226..af200fc69 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -5,15 +5,14 @@ package dev.gitlive.firebase import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind as StructureKind) { - StructureKind.CLASS, StructureKind.OBJECT -> (value as Json).let { json -> +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decodeDouble: (value: Any?) -> Double?): CompositeDecoder = when(descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Json).let { json -> FirebaseClassDecoder(decodeDouble, js("Object").keys(value).length as Int, { json[it] != undefined }) { desc, index -> json[desc.getElementName(index)] } @@ -24,4 +23,9 @@ actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, decode StructureKind.MAP -> (js("Object").entries(value) as Array>).let { FirebaseCompositeDecoder(decodeDouble, it.size) { _, index -> it[index/2].run { if(index % 2 == 0) get(0) else get(1) } } } + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } + +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") +actual fun getPolymorphicType(value: Any?, discriminator: String): String = + (value as Json)[discriminator] as String diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 5a7ce7a0f..b5f5aeaef 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -4,12 +4,13 @@ package dev.gitlive.firebase +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.js.json -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): CompositeEncoder = when(descriptor.kind as StructureKind) { +actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> Array(descriptor.elementsCount) { null } .also { value = it } .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it[index] = value } } @@ -19,8 +20,16 @@ actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): Compo value = map FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> if(index % 2 == 0) lastKey = value as String else map[lastKey] = value } } - StructureKind.CLASS, StructureKind.OBJECT -> json() + StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> json() .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, positiveInfinity) { _, index, value -> it[descriptor.getElementName(index)] = value } } + .let { FirebaseCompositeEncoder( + shouldEncodeElementDefault, + positiveInfinity, + setPolymorphicType = { discriminator, type -> + it[discriminator] = type + }, + set = { _, index, value -> it[descriptor.getElementName(index)] = value } + ) } + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") }