Suppose there is one interface , and 2 (or more) implementations :
interface IRunnable {
fun run()
}
class Horse : IRunnable {
override fun run() { println("horse running") }
}
class Dog : IRunnable {
override fun run() { println("dog running") }
}
I want to achieve the most succinct JSON for network transmission, that is , {"runnable":"H"}
for Horse and {"runnable":"D"}
for Dog
If I cannot modify the interface and implementations , I want to serialize the interface/implementations to JSON , according to Kotlin's documentation , I have to write a custom serializer , and use SerializersModule
to achieve the goal.
Suppose there are only Horse
and Dog
implementing IRunnable
, This is what I've done :
class RunnableSerializer : KSerializer<IRunnable> {
override val descriptor: SerialDescriptor
get() = StringDescriptor.withName("runnable")
override fun serialize(encoder: Encoder, obj: IRunnable) {
val stringValue = when (obj) {
is Horse -> { "H" }
is Dog -> { "D" }
else -> { null }
}
stringValue?.also {
encoder.encodeString(it)
}
}
override fun deserialize(decoder: Decoder): IRunnable {
return decoder.decodeString().let { value ->
when(value) {
"H" -> Horse()
"D" -> Dog()
else -> throw RuntimeException("invalid $value")
}
}
}
}
But when I try to transform a Horse
to JSON ...
class RunnableTest {
private val logger = KotlinLogging.logger { }
@ImplicitReflectionSerializer
@Test
fun testJson() {
val module1 = serializersModuleOf(IRunnable::class, RunnableSerializer())
Json(context = module1).stringify(IRunnable::class.serializer(), Horse()).also {
logger.info("json = {}", it)
}
}
}
It outputs error :
kotlinx.serialization.SerializationException:
Can't locate argument-less serializer for class IRunnable. For generic classes, such as lists, please provide serializer explicitly.
How to achieve something like
{"runnable":"H"}
or {"runnable":"D"}
in this case ?
Thanks.
environments :
<kotlin.version>1.3.60</kotlin.version>
<serialization.version>0.14.0</serialization.version>
updated , full error message :
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class IRunnable. For generic classes, such as lists, please provide serializer explicitly.
at kotlinx.serialization.PlatformUtilsKt.serializer(PlatformUtils.kt:12)
at RunnableTest.testJson(RunnableTest.kt:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
What you are trying to achieve is called polymorphic serialization, which kotlinx.serialization supports. In short: it requires you to register all deriving types of IRunnable
as polymorphic in the SerialModule
. For your code it should look somewhat as follows:
val serialModule = SerializersModule {
polymorphic(IRunnable::class) {
Horse::class with HorseSerializer
Dog::class with DogSerializer
}
}
Currently, you only register IRunnable
, which as the exception correctly indicates, cannot be initialized. How would you initialize an interface? You can't. Instead, you want to know which specific deriving type to initialize. This requires to embed type information within the JSON data, which is what polymorphic serialization does.
In addition, you do not necessarily have to implement a custom serializer. As per the documentation you link to, you can create external serializers for library classes. The following might suffice, as the kotlinx.serialization compiler plugin will try to generate a suitable serializer for you, similar to if you were to add @Serializable
on the class directly:
@Serializer(forClass = Horse::class)
object HorseSerializer {}
@Serializer(forClass = Dog::class)
object DogSerializer {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With