Как правильно использовать наследование классов в Kotlin в сочетании с Kotlinx Serialization

У меня есть простая иерархия, содержащая следующее:

  • абстрактный класс BaseItem
  • открытый элемент класса: BaseItem
  • класс Рюкзак: Предмет

Все они должны работать с сериализацией Kotlinx. Все шло нормально, пока я не добавил класс Backpack. Я использую версию 1.4.32 сериализации Kotlinx.

Вот моя иерархия классов в деталях

// 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
)

IDE показывает мне следующее сообщение для аннотации @Serialization, прикрепленной к классу Backpack.

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

Мне не удалось выяснить, как это сделать, чтобы исправить это


person xetra11    schedule 21.04.2021    source источник


Ответы (2)


Это потому, что параметры вашего конструктора не определены как свойства класса. Чтобы параметры были определены как свойства, вы должны добавить к параметрам val или var. Это устранит текущее сообщение об ошибке:

@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
)

Но это все равно не скомпилируется, так как вы получите Serializable class has duplicate serial name of property 'brand', either in the class itself or its supertypes для всех свойств, которые используются в обоих классах. Но в любом случае я немного удивлен дизайном, поскольку обычно не рекомендуется наследовать от не абстрактного класса. Не зная намерений, я задаюсь вопросом, не сработает ли что-то вроде этого и для вас:

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()

Я кстати. удалил @Serializable из BaseItem, поскольку в нем нет необходимости, потому что класс в любом случае является абстрактным и, следовательно, не будет сериализован вообще. Я также сделал ваши классы data class, потому что у меня сложилось впечатление, что они в основном существуют для хранения данных и обычно реализуются с помощью data class. Я оставил многие var, которые я вижу, поскольку я не знаю причин для них, но небольшой намек с моей стороны заключается в том, что вам следует предпочесть val var, особенно в data class. var в этом сценарии мне кажется запахом кода, и вы можете подумать об использовании вместо этого val. Хорошим литератором для таких вещей является сама страница Kotlin: https://kotlinlang.org/docs/coding-conventions.html#idiomatic-use-of-language-features.

person Big_Foot1989    schedule 21.04.2021

Из Проектирование сериализуемой иерархии :

Чтобы сделать иерархию классов сериализуемой, свойства в родительском классе должны быть помечены как абстрактные, что делает абстрактный [родительский] класс тоже.

person dnault    schedule 21.04.2021
comment
Items, однако, не планируется использовать абстрактно - его экземпляры уже есть в кодовой базе и должны оставаться такими. - person xetra11; 21.04.2021
comment
@ xetra11 В этом случае BackPack не может расширять Item и быть сериализуемым. Возможно, вам придется настроить иерархию классов, как это предлагает Big_Foot1989. - person dnault; 22.04.2021