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

Support optional type parameter serializer for custom serializer

Open zsqw123 opened this issue 4 months ago • 0 comments

What is your use-case and why do you need this feature?

I want to jsonify an object which contains generic types, so I tried following codes at first.

@Serializable
class Request<T>(
    val body: T,
)

fun <T> submitRequest(content: T) {
    val request = Request(content)
    println(Json.encodeToString(request))
}

fun main() {
    submitRequest(1)
}

The error shows that I must use inline function with reified T.

Exception in thread "main" java.lang.IllegalArgumentException: Captured type parameter T of zsu.serial.demo.DemoKt.submitRequest from generic non-reified function. Such functionality cannot be supported because T is erased, either specify serializer explicitly or make calling function inline with reified T.
	at zsu.serial.demo.DemoKt.submitRequest(Demo.kt:21)
	at zsu.serial.demo.DemoKt.main(Demo.kt:18)
	at zsu.serial.demo.DemoKt.main(Demo.kt)

I know that kotlin needs object's "real" type to find proper serializer, but we need to wrap request body with very complex processing in our real business cases. Kotlin's inline is infective, it will inline our huge wrapper logic. So I'm trying to create custom serializer to solve this problem.

In following case, I store the bodyType in the wrapper class, and add custom serializer to serialize class by bodyType's serializer. Now we only needs to use inline when initialize this object, we don't need to inline every processing function invoke if we have multiple level wrappers (e.g. HttpWrap<RequestWrap<HeaderWrap<T>>>)

@Serializable(Request.Serializer::class)
class Request<T>(
    val body: T, val bodyType: KType,
) {
    class Serializer<T> : KSerializer<Request<T>> {
        // ... serialize by using bodyType to create a serializer
    }
}

inline fun <reified T> Request(body: T) = Request(body, typeOf<T>())

fun <T> submitRequest(request: Request<T>) {
    println(Json.encodeToString(request))
}

fun main() {
    submitRequest(Request(1))
}

But same error occurred after I run it. I found that kotlin serialization will try to resolve every typeParameter when build serializer even I don't use such information in my custom resolver.

So here is my temporary solution, transform to a new object without type parameter. But this solution needs us to call serializable function to convert object by manual.

class Request<T>(
    val body: T, val bodyType: KType,
) {
    fun serializable() = SerializableRequest(body, bodyType)
}

@Serializable(SerializableRequest.Serializer::class)
class SerializableRequest(val body: Any?, val bodyType: KType) {
    class Serializer : KSerializer<SerializableRequest> {
        // ... serialize by using bodyType to create a serializer
    }
}

inline fun <reified T> Request(body: T) = Request(body, typeOf<T>())

fun <T> submitRequest(request: Request<T>) {
    println(Json.encodeToString(request.serializable()))
}

fun main() {
    submitRequest(Request(1))
}

Describe the solution you'd like

I have a better solution if we can modify KSerializer's initialization. Mark type parameter serializer's type as nullable to indicates that this custom KSerializer don't need specified type parameter as input. Here shows a type TU with optional tSerializer and required uSerializer.

@Serializable(TU.Serializer::class)
class TU<T, U>(t: T, u: U) {
    class Serializer<T, U>(
        tSerializer: KSerializer<T>?, // optional, null if we cannot found T's Serializer
        uSerializer: KSerializer<U>
    ) : KSerializer<TU<T, U>>
}

or replace nullable KSerializer with a EmptyKSerializer. This will be easier because we don't need to modify existed compiler plugin codes, but not suit for express nullable

class Serializer<T, U>(
    tSerializer: KSerializer<*>, // * means any serializer is acceptable
    uSerializer: KSerializer<T>,
) : KSerializer<TU<T, U>>

object EmptyKSerializer : KSerializer<Nothing>

All codes can be found in my sample project: https://github.com/zsqw123/KSerializationSample


Related to some other discussions about serializer for generic types:

zsqw123 avatar Feb 19 '24 15:02 zsqw123