jsonkotlinserializationkotlinx.serialization

MutableList<T> not being serialized in Kotlin


I'm working on a simple chat server, using kotlin to learn it along the way

I've defined the class to store any message as

@Serializable
data class Message (
    var id: Int = messageCounter.getNextNumber(),
    var senderIP: String,
    var client: String,
    val timestamp: String = LocalDateTime.now().toString(),
    var message: String,

    var replies: MutableList<Message> = mutableListOf<Message>()
) {
    constructor(incomingMessage: IncomingMessage, ip: String) : this(
        messageCounter.getNextNumber(),
        ip,
        incomingMessage.client,
        LocalDateTime.now().toString(),
        incomingMessage.query,
        mutableListOf<Message>()
    )
}

And I'm using websocket for real time texting, sending data in JSON format

fun Application.configureSockets() {
    install(WebSockets) {
        pingPeriod = Duration.ofSeconds(15)
        timeout = Duration.ofSeconds(84600)
        maxFrameSize = Long.MAX_VALUE
        masking = false
    }
    routing {

        val clients = mutableSetOf<DefaultWebSocketSession>()

        webSocket("/chat") {
            try {
                val ipAddress = call.request.origin.remoteHost
                clients += this
                for (frame in incoming) {
                    if (frame is Frame.Text) {
                        val receivedText = frame.readText()
                        println("Received $receivedText, from $ipAddress")
                        val incomingMessage = Json.decodeFromString<IncomingMessage>(receivedText)

                        val reply = Message(
                            senderIP = ipAddress,
                            client = incomingMessage.client,
                            message = incomingMessage.query,
                        )

                        if (clients.size == 1) {
                            println("replying")
                            reply.message = "Client not connected!"
                            clients.first().send(Frame.Text(Json.encodeToString(reply)))
                        } else {
                            println("broadcasting")
                            clients.forEach { client ->
                                if (client.isActive) {    // checking this for scrolling
                                    client.send(Frame.Text(Json.encodeToString(reply)))
                                }
                            }
                        }
                    }
                }
            } catch (ex: Exception) {
                println(ex.stackTrace)
                clients -= this
            }
        }
    }
}

This is the sent/received output on screen:

{
    "id": 8,
    "senderIP": "192.168.1.173",
    "client": "me",
    "timestamp": "2024-09-09T13:30:11.419940",
    "message": "testing"
}

The replies aren't getting serialized, and I've no idea why!

While writing the same code in java, all I had to do was to use keep the list in a separate class and mark it serializable, but that appraoch isn't working here as well.

My preference will be not writing a custom serializer, as I've no knowledge of it and I think I'm missing something as a built-in class like MutableList should be serializable by default.


Solution

  • The solution to this is using
    @OptIn(ExperimentalSerializationApi::class) at class level
    and
    @EncodeDefault(EncodeDefault.Mode.ALWAYS) before declaring property/memeber

    More details at official docs, here:
    https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#defaults-are-not-encoded-by-default

    The updated version of my code:

    @Serializable
    @OptIn(ExperimentalSerializationApi::class)     // to change the default behaviour of serializer
    data class Message (
        var id: Int = messageCounter.getNextNumber(),
        var senderIP: String,
        var client: String,
        val timestamp: String = LocalDateTime.now().toString(),
        var message: String,
        @EncodeDefault(EncodeDefault.Mode.ALWAYS)       // to show the empty variables
        var replies: MutableList<Message> = mutableListOf<Message>()
    )