Skip to content

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

@YarnSphere

Description

@YarnSphere

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 SetSerializer or any other "built-in" collection serializer results in the same incorrect behaviour.
  • The custom serializer's deserialize function 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.3 with Kotlin 1.9.24
  • 0.90.0-RC2 with Kotiln 2.0.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    cantfixThis issue cannot be resolved at this point because of limitations in external libraries.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions