Rohen Giralt

    Rohen Giralt

    1 year ago
    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
    val json = Json {
        contextual(MyInterface::class, MyInterfaceSerializer)
    }
    but since that works on the KClass of the underlying type, I get an
    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

    dimitar_

    1 year ago
    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.
    rnett

    rnett

    1 year ago
    Polymorphic is deserialization only, see (and maybe leave your use case) https://github.com/Kotlin/kotlinx.serialization/issues/1317
    d

    dimitar_

    1 year ago
    @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 🙂
    rnett

    rnett

    1 year ago
    If you can define a serialize for each class, then yeah you should be fine, I read it the other way
    Rohen Giralt

    Rohen Giralt

    1 year ago
    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):
    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)
    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
    {
        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:
    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
    @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.
    d

    dimitar_

    1 year ago
    Why not this?
    For example, the output of
    /program/cloud1
    is:
    {
      "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.