Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.serializer
import kotlinx.serialization.serializerOrNull
import kotlin.reflect.KClass
Expand All @@ -28,19 +32,56 @@ object AppUtils {
// registered manually in serializersModule, we need both to support all cases
val serializer =
this::class.serializerOrNull() ?: json.serializersModule.getContextual(this::class)
return if (serializer != null) {
if (serializer != null) {
try {
@Suppress("UNCHECKED_CAST")
json.encodeToString(serializer as KSerializer<Any>, this)
return json.encodeToString(serializer as KSerializer<Any>, this)
} catch (e: SerializationException) {
logError(e)
mapper.writeValueAsString(this)
return mapper.writeValueAsString(this)
}
}
// Handle generic collection/map types where type params are erased at runtime
// and no serializer can be found via reflection alone

@fire-light42 fire-light42 Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write test cases for this type of behavior, it looks very easy to break things here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what to write for tests here. I have another method for this to recursively convert things to a JsonElement, more reliable maybe but a least a tiny bit less performant. The issue is that for things like setKey, the type ends up getting erased, which means we have a few other options also, make setKey always use reified for serialized classes, or pass the type directly. I am not 100% sure on the best path here.

return try {
@Suppress("UNCHECKED_CAST")
when (this) {
is Array<*> -> json.encodeToString(ListSerializer(elementSerializer()), this.toList())
is Set<*> -> json.encodeToString(SetSerializer(elementSerializer()), this)
is List<*> -> json.encodeToString(ListSerializer(elementSerializer()), this)
is Collection<*> -> json.encodeToString(ListSerializer(elementSerializer()), this.toList())
is Map<*, *> -> json.encodeToString(MapSerializer(String.serializer(), valueSerializer()), this as Map<String, Any?>)
else -> mapper.writeValueAsString(this)
}
} else {
} catch (e: SerializationException) {
logError(e)
mapper.writeValueAsString(this)
}
}

private fun elementSerializerForClass(kClass: KClass<*>): KSerializer<Any?> {
val serializer = kClass.serializerOrNull()
?: json.serializersModule.getContextual(kClass)
?: throw SerializationException("No serializer found for element type ${kClass.simpleName}")
@Suppress("UNCHECKED_CAST")
return serializer as KSerializer<Any?>
}

private fun Collection<*>.elementSerializer(): KSerializer<Any?> {
val elementClass = this.firstOrNull()?.let { it::class } ?: String::class
return elementSerializerForClass(elementClass)
}

private fun Array<*>.elementSerializer(): KSerializer<Any?> {
val elementClass = this.firstOrNull()?.let { it::class } ?: String::class
return elementSerializerForClass(elementClass)
}

private fun Map<*, *>.valueSerializer(): KSerializer<Any?> {
val elementClass = this.values.firstOrNull()?.let { it::class } ?: String::class
return elementSerializerForClass(elementClass)
}

@InternalAPI
fun <T : Any> parseJson(value: String, kClass: KClass<T>): T {
val serializer = kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass)
Expand Down