Search code examples
kotlinkmongo

KMongo custom serializer: readEndDocument can only be called when State is END_OF_DOCUMENT, not when State is VALUE


I'm trying to make a custom serializer for the Color class using the kmongo-coroutine-serialization dependency. I'm getting an exception when doing it saying:

Exception in thread "main" org.bson.BsonInvalidOperationException: readEndDocument can only be called when State is END_OF_DOCUMENT, not when State is VALUE.


Document I test it on as json

{
    "_id": {
        "$oid": "61fe4f745064370bd1473c41"
    },
    "id": 1,
    "color": "#9ac0db"
}

ExampleDocument class:

@Serializable
data class ExampleDocument(
    @Serializable(with = ColorHexSerializer::class) val color: Color
)

ColorHexSerializer object: for testing purposes I always return blue

internal object ColorHexSerializer : KSerializer<Color> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("color_hex", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Color) {
        val hex = String.format("#%06x", value.rgb and 0xFFFFFF)
        encoder.encodeString(hex)
    }

    override fun deserialize(decoder: Decoder): Color {
        return Color.BLUE
    }
}

main function:

suspend fun main() {
    registerSerializer(ColorHexSerializer)
    val document =
        KMongo.createClient("connectionString")
            .getDatabase("database")
            .getCollectionOfName<ExampleDocument>("testing")
            .find(eq("id", 1))
            .awaitFirst()
}


Converting the bson document to json and deserialize it then with kotlinxserialization works just fine. Can anyone help me here?

Thanks in advance


Solution

  • From the documentation of Decoder:

    Deserialization process takes a decoder and asks him for a sequence of primitive elements, defined by a deserializer serial form, while decoder knows how to retrieve these primitive elements from an actual format representations.

    and

    To be more specific, serialization asks a decoder for a sequence of "give me an int, give me a double, give me a list of strings and give me another object that is a nested int"

    You need to specify all the values you read from the decoder in order. All values written also need to be read. It just seems that (at least in your case) kotlinx.serialization ignores the mistake of leftover content to read while kmongo throws an exception.

    By returning the color blue by default, you skip the step of reading the string resulting in the string to be left.

    Since there is a String left, the state of the object is VALUE (there is a value left to read) while you try to finish reading the document.

    override fun deserialize(decoder: Decoder): Color {
        decoder.decodeString()
        return Color.BLUE
    }