pdvrieze / xmlutil

XML Serialization library for Kotlin
https://pdvrieze.github.io/xmlutil/
Apache License 2.0
377 stars 30 forks source link

Values serialized as collections via custom serializers aren't decoded correctly #213

Open YarnSphere opened 3 months ago

YarnSphere commented 3 months ago

Description

Consider the following toy example:

@Serializable
data class User(val name: String, val shoppingCart: ShoppingCart)

@Serializable(with = ShoppingCartSerializer::class)
data class ShoppingCart(val items: MutableList<Item>) {
    val total: Double get() = items.sumOf { it.price }
}

@Serializable
data class Item(val name: String, val price: Double)

Where ShoppingCartSerializer is a custom serializer, which serializes ShoppingCart as a list:

class ShoppingCartSerializer : KSerializer<ShoppingCart> {
    private val listSerializer = ListSerializer(Item.serializer())

    override val descriptor: SerialDescriptor =
        SerialDescriptor("org.example.ShoppingCart", listSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: ShoppingCart): Unit =
        encoder.encodeSerializableValue(listSerializer, value.items)

    override fun deserialize(decoder: Decoder): ShoppingCart =
        ShoppingCart(decoder.decodeSerializableValue(listSerializer).toMutableList())
}

The following test, which should pass, does not:

val data = User(
    "Alice",
    ShoppingCart(mutableListOf(Item("T-Shirt", 20.0), Item("Boots", 50.0)))
)

@Test
fun testCustomCollectionXmlSerialization() {
    val encodedXml = XML.encodeToString(data)
    val decodedXml = XML.decodeFromString<User>(encodedXml)

    assertEquals(
        """<User name="Alice"><Item name="T-Shirt" price="20.0"/><Item name="Boots" price="50.0"/></User>""",
        encodedXml
    )
    assertEquals(data, decodedXml)
}

The first assert passes, i.e., encoding is correct. The second assert, however, fails with:

Expected :User(name=Alice, shoppingCart=ShoppingCart(items=[Item(name=T-Shirt, price=20.0), Item(name=Boots, price=50.0)]))
Actual   :User(name=Alice, shoppingCart=ShoppingCart(items=[Item(name=Boots, price=50.0)]))

I.e., decoding drops all items of the collection but the last.

Notes

Reproduction

The code above showcasing the issue is available at: https://github.com/YarnSphere/xmlutil-custom-collection-serialization

Versions tested

pdvrieze commented 3 months ago

Unfortunately this cannot work currently as list deserialization explicitly uses the AbstractCollectionSerializer to handle incremental reading of list elements. To avoid that the deserializer would need to buffer all list entries and only provide them at the end, as list.

YarnSphere commented 3 months ago

I see, that is indeed unfortunate. Appreciate the prompt reply!