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.
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>()
)