0

I have the following Kotlin code using Kotlin Serialization:

package com.renault.rclib

import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

abstract class Processor<T : Any> {
    protected fun process(data: T) {
        // Do stuff
        val json = Json.encodeToString(data) // <= Cannot use 'T' as reified type parameter. Use a class instead.
        // Do stuff with json
        println(json)
    }
}

@Serializable
data class Foo(val f: Int)

class FooProcessor : Processor<Foo>() {
    fun doSomething() {
        // ...
        process(getFooFromSomewhere())
        // ...
    }

    private fun getFooFromSomewhere() = Foo(17)
}

fun main() {
    FooProcessor().doSomething()
}

Essentially, I have a generic superclass parameterized by T that exposes a method process that takes an instance of T and serializes it to JSON and then does something with it. There are supposed to be many subclasses (here FooProcessor) for specific types (here Foo), all of which are annotated @Serializable.

The code above doesn't work, I get a compiler error Cannot use 'T' as reified type parameter. Use a class instead..

I understand the error, I think - the type parameter says it can be anything (except for null), even something that is not annotated @Serializable, so the compiler cannot guarantee that all of the types have a 'serializer'. What works is this:

package com.renault.rclib

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.Json

abstract class Processor<T : Any>(
    private val serializer: SerializationStrategy<T>,
) {
    protected fun process(data: T) {
        // Do stuff
        val json = Json.encodeToString(serializer, data)
        // Do stuff with json
        println(json)
    }
}

@Serializable
data class Foo(val f: Int)

class FooProcessor : Processor<Foo>(
    Foo.serializer(),
) {
    fun doSomething() {
        // ...
        process(getFooFromSomewhere())
        // ...
    }

    private fun getFooFromSomewhere() = Foo(17)
}

fun main() {
    FooProcessor().doSomething()
}

where every subtype must explicitly pass the SerializationStrategy. This is a bit cumbersome for implementers of the subclasses.

Ideally, there is some kind of an interface that each @Serializable class implements and the compiler knows that such classes have a serializer, so I could define the superclass as

abstract class Processor<T : IsAnnotatedWithSerialisable>(

and subclasses would not need to explicitly pass the serializer. Unfortunately, there seems to be no such thing.

For other libraries that I know, like Gson or Jackson, I can throw whatever at them and they will serialize it, but they use reflection and I can't have this overhead in this case. Is explicit passing of the serializer the only way, or is there something I'm missing?

EDIT: I found a solution that does work but it a lot of magic:

abstract class Processor<T : Any> {
    protected fun process(data: T) {
        val serializer = serializer(data::class.createType())
        // Do stuff
        val json = Json.encodeToString(serializer, data)
        // Do stuff with json
        println(json)
    }
}

but this creates a serializer each time. No idea about the performance hit that this produces (if any).

wujek
  • 10,112
  • 12
  • 52
  • 88
  • 1
    Rather than having each processor provide the serialised, how about having each `T` provide it? See the second half of [this answer of mine](https://stackoverflow.com/a/74847409/5133585) for how you could do this. – Sweeper Aug 05 '23 at 04:48
  • @Sweeper Looks like my question is a duplicate, and I think it should be closed as such. The solution you describe at the beginning of your answer is what I have as well. The custom interface etc. is actually requiring much more work for subclass implementors than just passing a Serializer. I also slept on it and I think it is Ok to required passing the Serializer, as it is also a kind of a compile-time check that only subclasses for valid types (that have a Serializer) can be used, so I think I will stick to that. – wujek Aug 05 '23 at 07:43

0 Answers0