I want a method on a parent Interface / Abstract Class that makes use of generics methods to pass in the class of the implementing class.
interface Domain {
fun toJSON(): String { return Json.encodeToString(this) }
}
@Serializable
class User: Domain {
val a: Int
}
This doesn't work since Json.encodeToString
doesn't know the class of 'this'.
@Serializable
seems to implement KSerializer
so in theory I could require domain to descend from it, but that interface is templated. And marking the implementing class @Serializable
doesn't seem to implement KSerializer
until compile time so creates errors.
How do I implement this toJSON()
method or tell Domain that its implementers must be @Serializable
/ KSerializer
?
I have also tried:
interface Domain<T> {
fun toJSON(): String { return Json.encodeToString(this) }
}
@Serializable
class User: Domain<User> {
val a: Int
}
But this results in:
kotlin.IllegalStateException: Only KClass supported as classifier, got T
One additional complication in all this is that I'm attempting to do this in KMM.
It does not work, because encodeToString()
resolves the type at compile time, not at runtime (it uses reified type).
Maybe there is a better way, but you can do this by acquiring a serializer manually, resolving the type at runtime:
fun toJSON(): String {
val serializer = Json.serializersModule.serializer(this::class.createType())
return Json.encodeToString(serializer, this)
}
Note this will only work on JVM. If you need to do this in KMM then be aware that multiplatform reflection is complicated and/or it needs some time to mature. kotlinx.serialization
provides a way to do this, but there are warnings about possible inconsistent behavior between platforms:
@OptIn(InternalSerializationApi::class)
fun toJSON(): String {
@Suppress("UNCHECKED_CAST")
val serializer = this::class.serializer() as KSerializer<Any?>
return Json.encodeToString(serializer, this)
}