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).