kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

"Serializer for class List is not found" on Native.

Open Nek-12 opened this issue 1 year ago • 2 comments

kotlinx.serialization.SerializationException: Serializer for class 'List' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.

Environment

  • Kotlin version: 2.0.10
  • Library version: 1.7.1
  • Kotlin platforms: Native (iOS) + Android
  • Gradle version: 8.9

We are using a reified function to deserialize response body from Ktor on Native using Darwin engine. The type parameter is an object containing a map with values of Lists of objects serialized using a polymorphic serializer. (I will add the example code a bit later).

On Android, the code works correctly without any modifications. Native however fails finding a serializer for List class, which is strange and looks like a compiler error. This is the only instance where we are using custom serializer modules / polymorphic so cannot really tell if this is something with the class.

Nek-12 avatar Aug 12 '24 17:08 Nek-12

What I suspect is that he "List" class is actually a native type, not kotlin.List one (for which ListSerializer is the actual serializer).

pdvrieze avatar Aug 13 '24 18:08 pdvrieze

Not sure about that since all that code is in common main source set and never left it. I am suspicious of the polymorphic serialization on native because that class / endpoint is the only one for which polymorphic serialization is used in the entire project.

Nek-12 avatar Aug 13 '24 18:08 Nek-12

Guys, this is still affecting us. Still unable to run polymorphic requests on native. Kotlin 2.0.20 did not help. Do you have any workarounds in mind? I added some sample code

Nek-12 avatar Aug 31 '24 12:08 Nek-12

Found a workaround:

client.post<String, _>("/sync", MultiSyncRequest(data, lastSynced)) {
        skipSavingBody()
    }.tryMap {
        json.decodeFromString(ListSerializer(SyncResult.serializer(PolymorphicSerializer(SyncedModel::class))), it)
    }

The key is to explicitly and manually deserialize from string without using reified. I am pretty sure it's an issue with how the typeOf() is handled by k/n

Nek-12 avatar Aug 31 '24 13:08 Nek-12

I've tried to minimize reproducer a little big and got the following:

interface SyncedModel {
    val id: Int
}

@Serializable
@SerialName("entry")
data class Entry(
    override val id: Int = Random.nextInt(),
    val pointsDelta: Int,
) : SyncedModel


@Serializable
enum class SyncedEntityType {
    Entry, //...
}

@Serializable
data class MultiSyncValue<out R : SyncedModel>(
    val items: List<R>,
)

@Serializable
internal data class MultiSyncRequest(
    val data: Map<SyncedEntityType, MultiSyncValue<SyncedModel>>,
)

internal inline fun <reified T, reified R> post(
    url: String,
    body: R? = null,
    builder: () -> Unit = {},
): T = call<T, R>(url, "POST", body, builder)

internal inline fun <reified T, reified R> call(
    url: String,
    method: String,
    body: R? = null,
    builder: () -> Unit = {},
): T {
    println(jj.encodeToString(body))
    return jj.decodeFromString("""[{"type":"entry","id":0,"pointsDelta":42}]""")
}

internal val syncSerializerModule = SerializersModule {
    polymorphic(SyncedModel::class) {
        subclass(Entry.serializer())
    }
}

val jj = Json {
    serializersModule = syncSerializerModule
}

@Test
fun testReified() {
    println(post<List<SyncedModel>, _>("/sync", MultiSyncRequest(mapOf())))
}

On Native, it fails with

kotlinx.serialization.SerializationException: Serializer for class 'SyncedModel' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
To get enum serializer on Kotlin/Native, it should be annotated with @Serializable annotation.
To get interface serializer on Kotlin/Native, use PolymorphicSerializer() constructor function.

it is unfortunately true — because of https://youtrack.jetbrains.com/issue/KT-41339, we cannot get polymorphic serializer by KType, so specifying it explicitly as you've mentioned in workaround is the way to go.

Regardless List, however, I think it is a problem from Ktor side. From the looks of it, request function does not handle reified type arguments correctly. If you experience this problem with List of non-interface classes, I suggest reporting the issue directly to them.

sandwwraith avatar Sep 02 '24 17:09 sandwwraith

No, the only way this happens is with this polymorphic request. I think the cause is the issue you mentioned above, thanks, I didn't know about this caveat. I will keep track of that one

Nek-12 avatar Sep 02 '24 18:09 Nek-12