Question about responses and serialization: I have...
# ktor
n
Question about responses and serialization: I have this sealed class, and it contains success and error responses as member classes. Each inherit from from the base Response class:
Copy code
@Serializable
sealed class Response(
 @Contextual
 val statusCode: HttpStatusCode = HttpStatusCode.OK
) {
 @Serializable
 data class SuccessResponse<T>(
  val data : T? = null,
  val message : String? = null
 ) : Response()

 @Serializable
 data class ErrorResponse<T>(
  val exception : T? = null,
  val message : String? = null
 ) : Response()
}
However it fails to serialize and I get runtime errors. If I attempt to convert the response to Json using
call.respond(statusCode, Json.encodeToString(result))
then I get:
Copy code
kotlinx.serialization.SerializationException: Class 'String' is not registered for polymorphic serialization in the scope of 'Any'.
Mark the base class as 'sealed' or register the serializer explicitly.
If I don’t and just attempt to call
call.respond(statusCode, result)
, then I get this error instead:
Copy code
kotlinx.serialization.SerializationException: Serializer for class 'ErrorResponse' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
Not sure if this is an issue with how I am using serialization, or if this is even the recommended way of dealing with responses?
g
You are not configuring the right way the serialization, check kotlinx.serialization open polymorphism:https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism .
About the response, it seems ok to me but idk if there is general guide or something to help you. FYI: I'm using something similar to respond aka :
data: Any
,
error :Error
n
The response object is initialized in the repository and is then passed to the route handler, where I want to send it like so:
Copy code
val result = Repository.myMethod(request)

call.respond(result.statusCode, Json.encodeToString(result))
I am using Ktor 2.0, and serialization is configured as a plugin:
Copy code
fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json()
  }
}
What other configuration does serialization need?
g
Although im not using ktor, probably you need to register at compile time the possible types the`Any` type can be. e.g.:
Copy code
val responseModule = SerializersModule {
    polymorphic(Response::class) {
        subclass(SuccessResponse.serializer(PolymorphicSerializer(Any::class)))
    }
}
refering directly this example in docs: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#polymorphism-and-generic-classes In your example something like this could be ok :
Copy code
val responseModule = SerializersModule {
    polymorphic(Response::class) {
        subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
    }

subclass(ErrorResponse.serializer(PolymorphicSerializer(Any::class)))
    }
}
I have not tested the code, but i hope you get the idea
n
Thank you, but this is still giving me the same error:
Copy code
val module = SerializersModule {
  polymorphic(Response::class) {
    subclass(Response.SuccessResponse.serializer(PolymorphicSerializer(Any::class)))
    subclass(Response.ErrorResponse.serializer(PolymorphicSerializer(Any::class)))
  }
}
val format = Json { serializersModule = module }

call.respond(result.statusCode, format.encodeToString(result))
results in
Class 'String' is not registered for polymorphic serialization in the scope of 'Any'.
Mark the base class as 'sealed' or register the serializer explicitly.
I see in one of the example that they also include an extension function in the SerializersModule block:
Copy code
val module = SerializersModule { 
    fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() {
        subclass(OwnedProject::class)
    }
    polymorphic {
      ...
    }
}
but that also does not work, same error message.
g
There is an open issue for that: https://github.com/Kotlin/kotlinx.serialization/issues/1252 As per the issue a simple workaround is :
Copy code
/**
 * A [String] serializer as an object, and workaround for
 * [kotlinx.serialization-1252](<https://github.com/Kotlin/kotlinx.serialization/issues/1252>).
 */
internal object StringSerializer : KSerializer<String> {

    @Serializable
    @SerialName("String")
    data class StringSurrogate(val value: String)

    override val descriptor: SerialDescriptor = StringSurrogate.serializer().descriptor

    override fun serialize(encoder: Encoder, value: String) {
        StringSurrogate.serializer().serialize(encoder, StringSurrogate(value))
    }

    override fun deserialize(decoder: Decoder): String {
        return decoder.decodeSerializableValue(StringSurrogate.serializer()).value
    }
}
and register the StringSerializer in the moduleBuilder
So the final form can be smth like this:
Copy code
private val module = SerializersModule {
    polymorphic(Response::class) {
        subclass(Response.SuccessResponse.serializer(PolymorphicSerializer(Any::class)))
        subclass(Response.ErrorResponse.serializer(PolymorphicSerializer(Any::class)))
        polymorphic(Any::class) {
            subclass(StringSerializer::class, StringSerializer.serializer)
        }
    }
}
n
Oh wow, I did not realize about the open issue. Honestly, that seems like a lot of overhead for something so basic, and it makes me wonder whether the original approach of using a generic Response class with subclasses for the different response types is the correct approach. Ktor docs don’t give an examples of best practices, but it looks like this is not going to end up being clean or concise, which was my original intent. Out of curiosity, what do you use for your server framework? You said you don’t use Ktor.
g
I use springboot, imo: its ok to have a little bit of configuration for using kotlinx.serialization. i think its more like the trade-off for it's good performance. (you only need to register the classes which have
Any
Type as value). The code worked?
n
Works like a charm. I needed another polymorphic block for the value of data in SuccessResponse, but now it’s all working, and I feel like I understand the whole thing much better. Thank you for that.
👌 1
g
You are welcome