I receive requests like: `[2,"6bd18013-b33f-43bb-b...
# serialization
g
I receive requests like:
[2,"6bd18013-b33f-43bb-bf91-7745e5517dd2","StatusNotification",{"connectorId":1,"errorCode":"NoError","status":"Available"}]
and
[2,"5a3a42a2-212d-4815-9581-f64ce55fd6cc","Heartbeat",{}]
over websockets to which I then answer:
[3,"6bd18013-b33f-43bb-bf91-7745e5517dd2",{}]
and
[3,"5a3a42a2-212d-4815-9581-f64ce55fd6cc",{"currentTime":"2022-04-27T11:29:38.179938700"}]
. What is the most elegant way to deserialize, serialize and answer them?
At the moment I use classes, but I feel like there must be a more elegant way:
Copy code
val request = Json.decodeFromString<JsonArray>(message)

val payload = Call.payload(request)

val response = CallResult.response(request)

@Serializable
data class HeartbeatResponse(
    val currentTime: String,
): Response()

@Serializable
class Heartbeat(
): Request()

@Serializable
class Call(
){
    companion object {
        fun messageType(jsonArray: JsonArray): MessageType {
            return MessageType.fromTypeNr(Json.decodeFromJsonElement(jsonArray[0]))
        }
        private fun action(jsonArray: JsonArray): String {
            return Json.decodeFromJsonElement(jsonArray[2])
        }
        fun payload(jsonArray: JsonArray): Request {
            when(ActionType.fromTypeString(action(jsonArray))) {
                ActionType.Heartbeat -> {
                    return Json.decodeFromJsonElement<Heartbeat>(jsonArray[3])
                }
            }
        }
    }
}

@Serializable
class CallResult(
){
    companion object {
        private fun action(jsonArray: JsonArray): String {
            return Json.decodeFromJsonElement(jsonArray[2])
        }
        private fun uniqueId(jsonArray: JsonArray): String {
            return Json.decodeFromJsonElement(jsonArray[1])
        }
        private fun responseMessageType(): JsonElement {
            return Json.encodeToJsonElement(MessageType.toTypeNr(MessageType.CALL_RESULT))
        }
        private fun responseUniqueId(jsonArray: JsonArray): JsonElement {
            return Json.encodeToJsonElement(uniqueId(jsonArray))
        }
        fun response(jsonArray: JsonArray): JsonElement {
            return JsonArray(arrayListOf(responseMessageType(), responseUniqueId(jsonArray), payload(jsonArray)))
        }
        private fun payload(jsonArray: JsonArray): JsonElement {
            when(ActionType.fromTypeString(action(jsonArray))) {
                ActionType.Heartbeat -> {
                    return Json.encodeToJsonElement(HeartbeatResponse(LocalDateTime.now().toString()))
                }
            }
        }
    }
}
e
consider something like this
g
That looks very promising. But I run into two problems with it at the moment. 1.
Copy code
kotlinx.serialization.SerializationException: Class 'Heartbeat' is not registered for polymorphic serialization in the scope of 'RequestPayload'.
Mark the base class as 'sealed' or register the serializer explicitly.
2. When I add a second subclass to the ResponsePayload I get a type mismatch for the value which I can not resolve.
Copy code
sealed class ResponsePayload {
    @Serializable
    @SerialName("Heartbeat")
    data class Heartbeat(
        val currentTime: String
        ) : ResponsePayload()

    @Serializable
    @SerialName("BootNotification")
    data class BootNotification(
    val bootMessage: String
    ) : ResponsePayload()

    @InternalSerializationApi
    object SerializerWithoutDiscriminator : SerializationStrategy<ResponsePayload> {
        @OptIn(ExperimentalSerializationApi::class)
        override val descriptor: SerialDescriptor = buildSerialDescriptor("ResponsePayload", PolymorphicKind.SEALED)

        override fun serialize(encoder: Encoder, value: ResponsePayload) {
            val serializer = when (value) {
                is Heartbeat -> Heartbeat.serializer()
                is BootNotification -> BootNotification.serializer()
                else -> throw IllegalStateException("Unknown ResponsePayload")
            }
            encoder.encodeSerializableValue(serializer, value)
        }
    }
}
e
1. you used
@SerialName
? 2. with multiple, the serializer types are narrower. try
Copy code
when (value) {
    is Heartbeat -> encoder.encodeSerializableValue(Heartbeat.serializer(), value)
    is BootNotification -> encoder.encodeSerializableValue(BootNotification.serializer() value)
or performing an unchecked cast
g
Copy code
@Serializable
sealed class RequestPayload {
    @Serializable
    @SerialName("Heartbeat")
    object Heartbeat : RequestPayload()
}
I used this.
e
hmm. this would make 1 work
Copy code
val serializer = decoder.serializersModule.getPolymorphic(baseClass, serializedClassName.jsonPrimitive.content)
    ?: (baseClass.serializerOrNull() as? AbstractPolymorphicSerializer<T>)
        ?.findPolymorphicSerializerOrNull(decoder, serializedClassName.jsonPrimitive.content)
    ?: throw SerializationException(
but I feel like there should be a better way that doesn't require on library internals, I just don't know of it
g
Got it to work. Thanks a lot. 🙂
It only feels a bit strange that the RequestPayload subclasses are shown as not needed.