Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.33k stars 618 forks source link

Support for `serializer<@Contextual T>` #2767

Open UnknownJoe796 opened 1 month ago

UnknownJoe796 commented 1 month ago

The following incomplete code cannot be used because @Contextual doesn't work in serializer<T>() .

This code would allow for very short definitions of server endpoints that handle serialization across multiple Content-Types.

inline fun <reified IN, reified OUT> ServerSystem.postHandler(action: (IN) -> OUT) {
    // Get the types for documentation purposes
    val inputType = serializer<IN>()
    val outputType = serializer<OUT>()
    // ... other setup ...
    // set up the underlying server library's request handler
    handler { request ->
        val format = Formats.lookup(headers["Content-Type"])
        val input = format.decodeFromBody(inputType, request.body)
        val output = action(input)
        return@handler HttpResponse(
            body = Formats.lookup(headers["Accept"]).encodeToBody(outputType, output)
        )
    }
}

// ...

val lookup = postHandler { input: @Contextual UUID ->
    database.get(input)
}

Attempting to use this crashes because the @Contextual annotation isn't carried through.

println(serializerOrNull(typeOf<@Contextual java.util.UUID>()))  // prints null

What I would expect is to get a ContextualSerializer for the type.

xiaozhikang0916 commented 1 month ago

The context is registered in the Serializers module, which means you need to provide a customized format. And if a customized format is provided, you may need not the @Contextual here.

pdvrieze commented 1 month ago

@UnknownJoe796 As @xiaozhikang0916 says, you can just look up the serializer from the module. serializerOrNull is for statically determined serializers. Contextual needs a context.

sandwwraith commented 1 month ago

I'm not sure what you are trying to achieve here since a) the @Contextual annotation has meaning only inside the @Serializable class b) ContextualSerializer is never returned from the serializer<T> function since it requires the SerializersModule to work. However, there are serializer<T> extensions on the SerializersModule as well, so I believe you want to do the following:

class Uuid

object UuidSerializer: KSerializer<Uuid> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MyUuid", PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: Uuid) {
        TODO("Not yet implemented")
    }

    override fun deserialize(decoder: Decoder): Uuid {
        TODO("Not yet implemented")
    }
}

val myModule = SerializersModule {
    contextual(Uuid::class, UuidSerializer)
}

inline fun <reified IN> postHandler(action: (IN) -> Unit) {
    println(myModule.serializer<IN>().descriptor)
}

@Test
fun contextualDemo() {
    postHandler<Uuid> {  } // prints PrimitiveDescriptor(MyUuid)
}