Hey everybody! Does anybody know if it’s possible ...
# serialization
r
Hey everybody! Does anybody know if it’s possible to register for serialization (but not deserizalization) an interface? My use case is in a webserver; I want to be able to convert any class that implements some interface into JSON to return to a GET request. I was able to write a custom serializer for the interface fairly easily, but the issue comes in using it; I can do
Copy code
val json = Json {
    contextual(MyInterface::class, MyInterfaceSerializer)
}
but since that works on the KClass of the underlying type, I get an
Copy code
kotlinx.serialization.SerializationException: Serializer for class 'MyInterfaceImpl' is not found.
I could use the
encodeToString
method explicitly, rather than using contextual serialization, but since I’m using Ktor’s ContentNegotiation feature, I wouldn’t be able to retain any of its features (such as a correct Content-Type header, etc.) without a bunch more work. Finally, I could also register each subtype polymorphically, but that seems like • a lot of repeated code (since all serializers would be the same) • a little bit of a breach of encapsulation (I’d like to have the serializer in a separate module, since the interface and its implementations don’t need to know about how they’re being displayed to the user, and vice versa) Any ideas?
d
You should use open polymorphism to register your interface and subclasses: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism
And you won't need a custom serializer for your interface implementations.
r
Polymorphic is deserialization only, see (and maybe leave your use case) https://github.com/Kotlin/kotlinx.serialization/issues/1317
d
@rnett I think @Rohen Giralt’s case is more simple. I don’t think he needs a custom serializer for the interface “I want to be able to convert any class that implements some interface into JSON to return to a GET request.” So just standard configuration with
polymorphic
and
@Serializable
on the implementation classes should be ok.
Or maybe I’m wrong 🙂
r
If you can define a serialize for each class, then yeah you should be fine, I read it the other way
r
Thanks for the responses! @rnett was actually correct in what I meant to say, but the wording was a bit wonky there, so I definitely see where @dimitar_ was coming from. :-) A bit more concretely, I have a webserver project, and its purpose is to allow the user to run certain programs. I have a Program interface (simplified here):
Copy code
interface Program {
    val name: String
    val pid: Int
    var isRunning: Bool
}
Different implementations might be a
LocalCLIProgram
, which stops and runs a program via the command line;
CloudProgram
, which runs in the cloud somewhere, etc. Separately, I have a path (in the Ktor routing DSL)
Copy code
get("/program/{name}") {
    val program = programRepository[call.parameters["name"]]
    call.respond(program)
}
What I’d like is for, no matter the concrete Program class, a request to /program/programName returns some JSON like
Copy code
{
    name: "programName",
    pid: 1003
    isRunning: false
}
The easiest way would be to have a custom
SerializationStrategy
for the interface and just to use it to serialize any
Program
I’m returning to the user, but Ktor’s implementation requires contextual serialization. KT-1317 I think would have provided the perfect solution, so I’ll leave my use case there. I found some level of a workaround, though; I can manually serialize my
Program
to a JsonObject and pass that to Ktor, still giving me all the benefits of Ktor Content Negotiation while also being able to serialize my classes how I want. I also wrote a simple extension function to make it easier:
Copy code
suspend fun <T> ApplicationCall.respond(serializer: SerializationStrategy<T>, message: T) =
    respond(Json.encodeToJsonElement(serializer, message))
and it seems to work so far. :-)
I was thinking a bit more, actually, and it just be easier for me to write something like
Copy code
@Serializable
data class ProgramDisplay(
    val name: String,
    val pid: Int, 
    val isRunning: Boolean
) {
    constructor(program: Program) : this(
        program.name,
        program.pid,
        program.isRunning
    )
}
and just respond with this separate
ProgramDisplay
object rather than the
Program
itself. This to some extent is a workaround for KT-1317; instead of serializing the object itself you convert it to a new one and serialize that.
👍 1
d
Why not this?
For example, the output of
/program/cloud1
is:
Copy code
{
  "type": "cloudProgram",
  "name": "cloud1",
  "pid": 23,
  "isRunning": false
}
@Rohen Giralt If I got your use case right, I think this is the best approach. No need to do anything special.