Skip to content

Commit

Permalink
Adding Serialization fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Daeda88 committed Dec 19, 2023
1 parent eb56044 commit 816bd62
Show file tree
Hide file tree
Showing 58 changed files with 1,980 additions and 1,100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,41 @@

package dev.gitlive.firebase

import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.CompositeDecoder

actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind) {
StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map ->
FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index ->
val elementName = desc.getElementName(index)
if (desc.kind is PolymorphicKind && elementName == "value") {
map
} else {
map[desc.getElementName(index)]
}
}
}
StructureKind.LIST ->
when(value) {
is List<*> -> value
is Map<*, *> -> value.asSequence()
.sortedBy { (it) -> it.toString().toIntOrNull() }
.map { (_, it) -> it }
.toList()
else -> error("unexpected type, got $value when expecting a list")
}
.let { FirebaseCompositeDecoder(it.size) { _, index -> it[index] } }
StructureKind.MAP -> (value as Map<*, *>).entries.toList().let {
FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } }
}
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) {
StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false)
StructureKind.LIST -> (value as? List<*>).orEmpty().let {
FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] }
}

StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let {
FirebaseCompositeDecoder(
it.size,
settings
) { _, index -> it[index / 2].run { if (index % 2 == 0) key else value } }
}

is PolymorphicKind -> decodeAsMap(polymorphicIsNested)
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
(value as? Map<*,*>).orEmpty()[discriminator] as String

private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map ->
FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index ->
if (isNestedPolymorphic) {
if (index == 0)
map[desc.getElementName(index)]
else {
map
}
} else {
map[desc.getElementName(index)]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@

package dev.gitlive.firebase

import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlin.collections.set

actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) {
StructureKind.LIST -> mutableListOf<Any?>()
.also { value = it }
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> it.add(index, value) } }
.let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } }
StructureKind.MAP -> mutableListOf<Any?>()
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } }
StructureKind.CLASS, StructureKind.OBJECT -> mutableMapOf<Any?, Any?>()
.also { value = it }
.let { FirebaseCompositeEncoder(shouldEncodeElementDefault,
.let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } }
StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor)
is PolymorphicKind -> encodeAsMap(descriptor)
else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet")
}

private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf<Any?, Any?>()
.also { value = it }
.let {
FirebaseCompositeEncoder(
settings,
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")
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.gitlive.firebase

import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule

/**
* Settings used to configure encoding/decoding
*/
sealed class EncodeDecodeSettings {

/**
* The [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime
*/
abstract val serializersModule: SerializersModule
}

/**
* [EncodeDecodeSettings] used when encoding an object
* @property shouldEncodeElementDefault if `true` this will explicitly encode elements even if they are their default value
* @param serializersModule the [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime
*/
data class EncodeSettings(
val shouldEncodeElementDefault: Boolean = true,
override val serializersModule: SerializersModule = EmptySerializersModule(),
) : EncodeDecodeSettings()

/**
* [EncodeDecodeSettings] used when decoding an object
* @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime
*/
data class DecodeSettings(
override val serializersModule: SerializersModule = EmptySerializersModule(),
) : EncodeDecodeSettings()
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ internal fun <T> FirebaseEncoder.encodePolymorphically(
value: T,
ifPolymorphic: (String) -> Unit
) {
// If serializer is not an AbstractPolymorphicSerializer or if we are encoding this as a list, we can just use the regular serializer
// This will result in calling structureEncoder for complicated structures
// For PolymorphicKind this will first encode the polymorphic discriminator as a String and the remaining StructureKind.Class as a map of key-value pairs
// This will result in a list structured like: (type, { classKey = classValue })
if (serializer !is AbstractPolymorphicSerializer<*>) {
serializer.serialize(this, value)
return
}

// When doing Polymorphic Serialization with EncodeDecodeSettings.PolymorphicStructure.MAP we will use the polymorphic serializer of the class.
val casted = serializer as AbstractPolymorphicSerializer<Any>
val baseClassDiscriminator = serializer.descriptor.classDiscriminator()
val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
Expand All @@ -32,15 +38,15 @@ internal fun <T> FirebaseDecoder.decodeSerializableValuePolymorphic(
value: Any?,
deserializer: DeserializationStrategy<T>,
): T {
// If deserializer is not an AbstractPolymorphicSerializer or if we are decoding this from a list, we can just use the regular serializer
if (deserializer !is AbstractPolymorphicSerializer<*>) {
return deserializer.deserialize(this)
}

val casted = deserializer as AbstractPolymorphicSerializer<Any>
val discriminator = deserializer.descriptor.classDiscriminator()
val type = getPolymorphicType(value, discriminator)
val actualDeserializer = casted.findPolymorphicSerializerOrNull(
structureDecoder(deserializer.descriptor),
structureDecoder(deserializer.descriptor, false),
type
) as DeserializationStrategy<T>
return actualDeserializer.deserialize(this)
Expand All @@ -55,4 +61,3 @@ internal fun SerialDescriptor.classDiscriminator(): String {
}
return "type"
}

101 changes: 68 additions & 33 deletions firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,26 @@ import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer

@Suppress("UNCHECKED_CAST")
inline fun <reified T> decode(value: Any?): T {
inline fun <reified T> decode(value: Any?): T = decode(value, DecodeSettings())
inline fun <reified T> decode(value: Any?, settings: DecodeSettings): T {
val strategy = serializer<T>()
return decode(strategy as DeserializationStrategy<T>, value)
return decode(strategy as DeserializationStrategy<T>, value, settings)
}

fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T {
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T = decode(strategy, value, DecodeSettings())
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, settings: DecodeSettings): T {
require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" }
return FirebaseDecoder(value).decodeSerializableValue(strategy)
return FirebaseDecoder(value, settings).decodeSerializableValue(strategy)
}
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder
expect fun getPolymorphicType(value: Any?, discriminator: String): String

class FirebaseDecoder(internal val value: Any?) : Decoder {
class FirebaseDecoder(val value: Any?, internal val settings: DecodeSettings) : Decoder {

constructor(value: Any?) : this(value, DecodeSettings())

override val serializersModule: SerializersModule
get() = EmptySerializersModule()
override val serializersModule: SerializersModule = settings.serializersModule

override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor)
override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor, true)

override fun decodeString() = decodeString(value)

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

override fun decodeNull() = decodeNull(value)

override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value)
override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value, settings)

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

class FirebaseClassDecoder(
size: Int,
settings: DecodeSettings,
private val containsKey: (name: String) -> Boolean,
get: (descriptor: SerialDescriptor, index: Int) -> Any?
) : FirebaseCompositeDecoder(size, get) {
) : FirebaseCompositeDecoder(size, settings, get) {
private var index: Int = 0

override fun decodeSequentially() = false

override fun decodeElementIndex(descriptor: SerialDescriptor): Int =
(index until descriptor.elementsCount)
.firstOrNull { !descriptor.isElementOptional(it) || containsKey(descriptor.getElementName(it)) }
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return (index until descriptor.elementsCount)
.firstOrNull {
!descriptor.isElementOptional(it) || containsKey(
descriptor.getElementName(
it
)
)
}
?.also { index = it + 1 }
?: DECODE_DONE
}
}

open class FirebaseCompositeDecoder(
private val size: Int,
private val get: (descriptor: SerialDescriptor, index: Int) -> Any?
internal val settings: DecodeSettings,
private val get: (descriptor: SerialDescriptor, index: Int) -> Any?,
): CompositeDecoder {

override val serializersModule = EmptySerializersModule()
override val serializersModule: SerializersModule = settings.serializersModule

override fun decodeSequentially() = true

Expand All @@ -100,21 +110,30 @@ open class FirebaseCompositeDecoder(
index: Int,
deserializer: DeserializationStrategy<T>,
previousValue: T?
) = deserializer.deserialize(FirebaseDecoder(get(descriptor, index)))
) = decodeElement(descriptor, index) {
deserializer.deserialize(FirebaseDecoder(it, settings))
}

override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = decodeBoolean(get(descriptor, index))
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeBoolean)

override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = decodeByte(get(descriptor, index))
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeByte)

override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = decodeChar(get(descriptor, index))
override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeChar)

override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble(get(descriptor, index))
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeDouble)

override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = decodeFloat(get(descriptor, index))
override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeFloat)

override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = decodeInt(get(descriptor, index))
override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeInt)

override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = decodeLong(get(descriptor, index))
override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeLong)

override fun <T : Any> decodeNullableSerializableElement(
descriptor: SerialDescriptor,
Expand All @@ -123,19 +142,37 @@ open class FirebaseCompositeDecoder(
previousValue: T?
): T? {
val isNullabilitySupported = deserializer.descriptor.isNullable
return if (isNullabilitySupported || decodeNotNullMark(get(descriptor, index))) decodeSerializableElement(descriptor, index, deserializer, previousValue) else decodeNull(get(descriptor, index))
return if (isNullabilitySupported || decodeElement(descriptor, index, ::decodeNotNullMark)) {
decodeSerializableElement(descriptor, index, deserializer, previousValue)
} else {
decodeElement(descriptor, index, ::decodeNull)
}
}

override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = decodeShort(get(descriptor, index))
override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeShort)

override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = decodeString(get(descriptor, index))
override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) =
decodeElement(descriptor, index, ::decodeString)

override fun endStructure(descriptor: SerialDescriptor) {}

@ExperimentalSerializationApi
override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder =
FirebaseDecoder(get(descriptor, index))

decodeElement(descriptor, index) {
FirebaseDecoder(it, settings)
}

private fun <T> decodeElement(descriptor: SerialDescriptor, index: Int, decoder: (Any?) -> T): T {
return try {
decoder(get(descriptor, index))
} catch (e: Exception) {
throw SerializationException(
message = "Exception during decoding ${descriptor.serialName} ${descriptor.getElementName(index)}",
cause = e
)
}
}
}

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

private fun decodeNull(value: Any?) = value as Nothing?


Loading

0 comments on commit 816bd62

Please sign in to comment.