ktorio / ktor

Framework for quickly creating connected applications in Kotlin with minimal effort
https://ktor.io
Apache License 2.0
12.99k stars 1.06k forks source link

Usage of @ContextualSerialization annotated type in content negotiation #1879

Closed sandwwraith closed 4 years ago

sandwwraith commented 4 years ago

Subsystem Client and Server, kotlinx-serialization integration

Is your feature request related to a problem? Please describe. The issue originally described here: https://stackoverflow.com/questions/61492364/serialize-a-maplocaldate-object-with-ktor-and-kotlinx-serialization

When type annotated for contextual serialization inside another serializable type, there is no problem: kotlinx.serialization plugin automatically recognizes it and generates correct code that performs runtime lookup in SerialModule.

However, when such a type occurs on top-level position / generic position, it is a responsibility of client to pass correct serializer: e.g. in case of serializing LocalDate or Map<LocalDate, String>, kotlinx.serialization expects that user will call corresponding Json.stringify(ContextualSerializer(LocalDate::class)) or Json.stringify(MapSerializer(LocalDateSerializer(), String.serializer()).

This becomes an issue for ktor, where call.respond does not have an option to specify the serializer.

Describe the solution you'd like

SerialModule in kotlinx.serialization provides contextualOrDefault method which may be used in such cases, how it used in serial implicits, e.g. https://github.com/Kotlin/kotlinx.serialization/blob/ee4d7c7215e7ee851be1ccd833f03147820d2bf9/runtime/commonMain/src/kotlinx/serialization/SerialImplicits.kt#L63. This method has one drawback: It performs lookup regardless of any annotations on the type.

Another solution may be to somehow allow specifying serializer to use in call.respond, via e.g. a global map of serializers.

Bizilizi commented 4 years ago

As far as I can see, current changes solve only @ContextualSerialization problem and do nothing with Generic serialization.

Suppose you want to formalize responses in a way like this:

@Serializable
data class APIResponse<T>(
        val result: T,
        val code: Int = 200,
        val success: Boolean = true
)

In that situation serializer of APIResponse would have an argument, in order to implicitly declare nested serializer:

val serializer = APIResponse.serializer(ContentReview.serializer())

And by calling call.respond you would receive Can't locate argument-less serializer for class APIResponse, since with the current approach module.getContextual(value::class) returns null.

Please consider specifying serializer to use in call.respond in some way other than module.getContextual(...).

Whathecode commented 2 years ago

@e5l This issue is not resolved, and should not be closed. The main feature request here was:

... it is a responsibility of client to pass correct serializer ... This becomes an issue for ktor, where call.respond does not have an option to specify the serializer.

We have a similar issue with call.receive. No serializer can be retrieved through the type, since the type being deserialized is a star-projected type. We use a custom serializer which can perfectly deal with that. But, ktor prevents us from passing said custom serializer.

Registration as contextual in the SerializersModule can't solve that problem for us as far as I know.

If I understand correctly, call.respond and call.receive of ktor retrieve the statically defined serializer for the type parameter through KType. This may be convenient if (1) this serializer can be retrieved statically, and (2) it is the serializer you want. But, in essence, there is a one to many relationship between types and serializers. There will always be a need to be able to override the serializer to use. Retrieving a default serializer through the type parameter should be considered syntactic sugar, not the only possible behavior.

Our current workaround is simply to receive a String, and initialize the Json serializer ourselves, passing the custom serializer which ktor doesn't allow us to.

Whathecode commented 1 year ago

If you are using Mongodb with Ktor you will find that ObjectId (mongodb primary key) doesn't have Kotlin native serializer...

This is an old issue, but I don't see why that would be relevant re-reading what I wrote back then. Is this a comment on something I wrote, or reaffirming there is a need for the original reported issue?

Regardless, we implemented our own format to serialize to BSON using kotlinx.serialization, which does support ObjectId. Supporting different formats seems unrelated to what I commented on above which is about serializers (which can operate on any format).

urosjarc commented 1 year ago

@Whathecode This is a need also from my side that this issue activates again. And waw I admire that you guys did write your own serialization, I wasn't aware about that.