7

I have a simple hierarchy containing of the following:

  • abstract class BaseItem
  • open class Item : BaseItem
  • class Backpack : Item

They should all work with Kotlinx Serialization. It went fine until I added the Backpack class. I use version 1.4.32 of Kotlinx Serialization.

Here is my class hierarchy in detail

// Items.kt

@Serializable
sealed class BaseItem {
    abstract val id: String
    abstract val type: ItemType
    abstract var brand: String
    abstract var model: String
    abstract var imageLink: String
    abstract var traits: MutableList<Trait>
    abstract var implicitTraits: MutableList<Trait>
    abstract var details: MutableMap<String, String>
}

@Serializable
open class Item(
    override val id: String = UUID.randomUUID().toString(),
    override val type: ItemType = ItemType.UNDEFINED,
    override var brand: String,
    override var model: String,
    override var imageLink: String,
    override var traits: MutableList<Trait>,
    override var implicitTraits: MutableList<Trait>,
    override var details: MutableMap<String, String>,
) : BaseItem()

@Serializable // is marked as incorrect 
class Backpack(
    brand: String,
    model: String,
    imageLink: String,
    traits: MutableList<Trait>,
    implicitTraits: MutableList<Trait>,
    details: MutableMap<String, String>,
    var volume: Int
) : Item(
    type = ItemType.BACKPACK,
    brand = brand,
    model = model,
    imageLink = imageLink,
    traits = traits,
    implicitTraits = implicitTraits,
    details = details
)

The IDE is showing me the following message for the @Serialization annotation attached to the Backpack class.

This class is not serializable automatically because it has primary constructor parameters that are not properties

I was not able to find out what the working way it is to make this correct

Tamás Cseh
  • 3,050
  • 1
  • 20
  • 30
xetra11
  • 7,671
  • 14
  • 84
  • 159

2 Answers2

4

It's because the parameters of your constructor are not defined as properties of the class. To have the parameters defined as properties you have to add val or var to the parameters. This would resolve the error message you currently have:

@Serializable
class Backpack(
    override var brand: String,
    override var model: String,
    override var imageLink: String,
    override var traits: MutableList<Trait>,
    override var implicitTraits: MutableList<Trait>,
    override var details: MutableMap<String, String>,
    var volume: Int
) : Item(
    type = ItemType.BACKPACK,
    brand = brand,
    model = model,
    imageLink = imageLink,
    traits = traits,
    implicitTraits = implicitTraits,
    details = details
)

But this would still not compile as you would end up with Serializable class has duplicate serial name of property 'brand', either in the class itself or its supertypes for all properties that are used in both classes. But anyhow, I am a little bit surprised by the design as it usually not a good practice to inherit from a non abstract class. Without knowing the intentions I am wondering whether sth.like this would not work for you as well:

sealed class BaseItem {
    abstract val id: String
    abstract val type: ItemType
    abstract var brand: String
    abstract var model: String
    abstract var imageLink: String
    abstract var traits: MutableList<Trait>
    abstract var implicitTraits: MutableList<Trait>
    abstract var details: MutableMap<String, String>
}

@Serializable
data class Item(
    override val id: String = UUID.randomUUID().toString(),
    override val type: ItemType = ItemType.UNDEFINED,
    override var brand: String,
    override var model: String,
    override var imageLink: String,
    override var traits: MutableList<Trait>,
    override var implicitTraits: MutableList<Trait>,
    override var details: MutableMap<String, String>,
) : BaseItem()

@Serializable
data class Backpack(
    override val id: String = UUID.randomUUID().toString(),
    override val type: ItemType = ItemType.BACKPACK,
    override var brand: String,
    override var model: String,
    override var imageLink: String,
    override var traits: MutableList<Trait>,
    override var implicitTraits: MutableList<Trait>,
    override var details: MutableMap<String, String>,
    override var var volume: Int
) : BaseItem()

I btw. removed the @Serializable from BaseItem as it is unnecessary because the class is abstract anyhow and therefore won't be serialized at all. I also made your classes data class because my impression was that those basically exist to hold data and those are usually implemented with data class. I left the many var I see as I don't know the reasoning for those but a small hint from my side is that you should prefer val over var especially in data class. A var in this scenario feels like a code smell to me and you might want to consider using val instead. A good literatur for such things is the Kotlin page itself: https://kotlinlang.org/docs/coding-conventions.html#idiomatic-use-of-language-features

Big_Foot1989
  • 311
  • 1
  • 3
  • 11
2

From Designing Serializable Hierarchy:

To make hierarchy of classes serializable, the properties in the parent class have to be marked abstract, making the [parent] class abstract, too.

dnault
  • 8,340
  • 1
  • 34
  • 53
  • `Items` however is not planned to be used abstract - there are instances of it in the codebase already and should stay that way – xetra11 Apr 21 '21 at 19:10
  • 1
    @xetra11 In that case, `BackPack` cannot extend `Item` and also be serializable. You may need to adjust your class hierarchy as suggested by Big_Foot1989. – dnault Apr 21 '21 at 22:01