diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt index 3d9cc8944f5..a002deb3155 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -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 @@ -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, this) + return json.encodeToString(serializer as KSerializer, 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) + else -> mapper.writeValueAsString(this) } - } else { + } catch (e: SerializationException) { + logError(e) mapper.writeValueAsString(this) } } + private fun elementSerializerForClass(kClass: KClass<*>): KSerializer { + 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 + } + + private fun Collection<*>.elementSerializer(): KSerializer { + val elementClass = this.firstOrNull()?.let { it::class } ?: String::class + return elementSerializerForClass(elementClass) + } + + private fun Array<*>.elementSerializer(): KSerializer { + val elementClass = this.firstOrNull()?.let { it::class } ?: String::class + return elementSerializerForClass(elementClass) + } + + private fun Map<*, *>.valueSerializer(): KSerializer { + val elementClass = this.values.firstOrNull()?.let { it::class } ?: String::class + return elementSerializerForClass(elementClass) + } + @InternalAPI fun parseJson(value: String, kClass: KClass): T { val serializer = kClass.serializerOrNull() ?: json.serializersModule.getContextual(kClass) @@ -60,9 +101,8 @@ object AppUtils { inline fun 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() }.runCatching { - json.serializersModule.getContextual(T::class) - }.getOrNull() + val serializer = runCatching { serializer() }.getOrNull() + ?: json.serializersModule.getContextual(T::class) // Prefer Kotlin Serialization over Jackson if (serializer != null) { @@ -94,4 +134,4 @@ object AppUtils { null } } -} \ No newline at end of file +}