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
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 All @@ -60,9 +101,8 @@ object AppUtils {
inline fun <reified T : Any> parseJson(value: String): T {
// @Serializable generates a serializer at compile time; contextual serializers are
// registered manually in serializersModule, we need both to support all cases
val serializer = runCatching { serializer<T>() }.runCatching {
json.serializersModule.getContextual(T::class)
}.getOrNull()
val serializer = runCatching { serializer<T>() }.getOrNull()
?: json.serializersModule.getContextual(T::class)

// Prefer Kotlin Serialization over Jackson
if (serializer != null) {
Expand Down Expand Up @@ -94,4 +134,4 @@ object AppUtils {
null
}
}
}
}