Closed Nek-12 closed 1 month ago
What I suspect is that he "List" class is actually a native type, not kotlin.List
one (for which ListSerializer
is the actual serializer).
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.
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
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
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.
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
Environment
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.
Some sample code: