Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression with serialization using list of types that have registered contextual serializers #2323

Closed
rasharab opened this issue Jun 8, 2023 · 0 comments
Assignees
Labels

Comments

@rasharab
Copy link

rasharab commented Jun 8, 2023

This seems to be an issue in the kotlinx serialization land, but I'm not sure where the boundary ends between Ktor ContentNegotation plugin and Kotlinx serialization.

We have noticed a regression that was introduced with Ktor 2.2.* and kotlinx.serialization 1.4.1+ when serializing/deserializing lists of Contextual types, and it seems to be related to serialization caching.

The regression produces the following error:

Class 'ArrayList' is not registered for polymorphic serialization in the scope of 'List'.
To be registered automatically, class 'ArrayList' has to be '@Serializable', and the base class 'List' has to be sealed and '@Serializable'.
Alternatively, register the serializer for 'ArrayList' explicitly in a corresponding SerializersModule.
kotlinx.serialization.SerializationException: Class 'ArrayList' is not registered for polymorphic serialization in the scope of 'List'.
To be registered automatically, class 'ArrayList' has to be '@Serializable', and the base class 'List' has to be sealed and '@Serializable'.
Alternatively, register the serializer for 'ArrayList' explicitly in a corresponding SerializersModule.
	at app//kotlinx.serialization.internal.Abst

We believe the above exception is a byproduct of changes in this pr to implement a caching layer for serializers:

2825e21

In particular, this conditional:

https://github.com/Kotlin/kotlinx.serialization/blame/84d4af04a688286ad1b51a72130ccda691caff75/core/commonMain/src/kotlinx/serialization/Serializers.kt#L200

The supplied testbed was working at Ktor 2.1.3 and Kotlinx Serialization 1.4.0.

TESTBED:

You can configure the versions here:

https://github.com/NextChapterSoftware/kotlinx-serialization-bug/blob/cefc82ad210cbd683c8327188f9d8a8d5119af00/kotlinx-serialization-bug/build.gradle.kts#L18

// Good Versions
//val ktorVersion = "2.1.3"
//val kotlinxSerializationVersion = "1.4.0"

// Regression Versions
val ktorVersion = "2.3.1"
val kotlinxSerializationVersion = "1.5.1"

You should run this file:

https://github.com/NextChapterSoftware/kotlinx-serialization-bug/blob/main/kotlinx-serialization-bug/src/test/kotlin/TestSerialization.kt

import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerCN
import io.ktor.serialization.kotlinx.json.json
import io.ktor.serialization.kotlinx.serializerForTypeInfo
import io.ktor.server.application.*
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import io.ktor.server.testing.testApplication
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind.STRING
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.UUID

object UUIDAdapter : KSerializer<UUID> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", STRING)

    override fun serialize(encoder: Encoder, value: UUID) {
        encoder.encodeString(value.toString())
    }

    override fun deserialize(decoder: Decoder): UUID {
        return UUID.fromString(decoder.decodeString())
    }
}

class TestSerialization {
    @Test
    fun `test serialization regression for list of type with contextual adapter`() = testApplication {
        val testHttpClient = createClient {
            install(ContentNegotiation) {
                json(
                    Json {
                        serializersModule = SerializersModule {
                            contextual(UUID::class, UUIDAdapter)
                        }
                    }
                )
            }
            defaultRequest {
                contentType(ContentType.Application.Json)
            }
        }
        application {
            install(ServerCN) {
                json(
                    Json {
                        serializersModule = SerializersModule {
                            contextual(UUID::class, UUIDAdapter)
                        }
                    }
                )
            }
            routing {
                post("/echoUUIDs") {
                    val bytes = call.receive<List<UUID>>()
                    call.respond(bytes.toString())
                }
            }
        }

        val response = testHttpClient.post("/echoUUIDs") {
            setBody(listOf(UUID.randomUUID(), UUID.randomUUID()))
        }

        val result = response.body<String>()
        Assertions.assertTrue(result.isNotEmpty())
    }
}
@rasharab rasharab changed the title Regression with Json ContentNegotation using list of types that we have contextual adapters for Regression with serialization using list of types that have registered contextual serializers Jun 8, 2023
@shanshin shanshin self-assigned this Jun 8, 2023
shanshin added a commit that referenced this issue Jun 12, 2023
…extual types

Fixes #2323

In the current implementation of serializer lookup caching, if the serializer for the parameter was not found, the `serializerOrNull` function always returned null, without further searching for contextual serializers in the current SerializersModule.
woainikk pushed a commit that referenced this issue Jun 20, 2023
…extual types (#2331)

Fixes #2323

In the current implementation of serializer lookup caching, if the serializer for the parameter was not found, the `serializerOrNull` function always returned null, without further searching for contextual serializers in the current SerializersModule.
JesusMcCloud pushed a commit to a-sit-plus/kotlinx.serialization that referenced this issue Jul 5, 2023
…extual types (Kotlin#2331)

Fixes Kotlin#2323

In the current implementation of serializer lookup caching, if the serializer for the parameter was not found, the `serializerOrNull` function always returned null, without further searching for contextual serializers in the current SerializersModule.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants