xmlutil
xmlutil copied to clipboard
Values serialized as collections via custom serializers aren't decoded correctly
trafficstars
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
- Using a standard "collection" (
List,Set,Array, etc.) for the shopping cart directly instead of a class with a delegated serializer results in the decoding working as expected. - Changing the custom serializer's implementation to delegate to
SetSerializeror any other "built-in" collection serializer results in the same incorrect behaviour. - The custom serializer's
deserializefunction is called once for each item of the collection, instead of only being called once for the whole collection. - The kotlinx.serialization JSON serializer has no problems with this example.
Reproduction
The code above showcasing the issue is available at: https://github.com/YarnSphere/xmlutil-custom-collection-serialization
Versions tested
0.86.3with Kotlin1.9.240.90.0-RC2with Kotiln2.0.0