Отсутствует поле идентичности с полиморфной (де) сериализацией в Котлине с Джексоном

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

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = NetCommand.AddEntity::class, name = "AddEntity"),
    JsonSubTypes.Type(value = NetCommand.RemoveEntity::class, name = "RemoveEntity"),
    JsonSubTypes.Type(value = NetCommand.MoveEntity::class, name = "MoveEntity"),
    JsonSubTypes.Type(value = NetCommand.SpeakEntity::class, name = "SpeakEntity"),
    JsonSubTypes.Type(value = NetCommand.AddItem::class, name = "AddItem")
)
sealed class NetCommand {
    class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
    class RemoveEntity(val id: Long) : NetCommand()
    class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
    class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
    class AddItem(val id: Long, val item: Item) : NetCommand()
}

Идея в том, что я могу передать коллекцию (ArrayList) из NetCommand для второго приложения, и пусть они будут правильно десериализованы в соответствующий подкласс.

Я также написал простой тест, чтобы помочь мне перебрать различные конфигурации аннотаций / картографа Джексона:

val command = NetCommand.AddEntity(1, TilePosition(0, 0), Character.KNIGHT)
val commandList: ArrayList<NetCommand> = ArrayList()
commandList.add(command)

val mapper = jacksonObjectMapper()

val commandListString = mapper.writeValueAsString(commandList)
val resultList = mapper.readValue<ArrayList<NetCommand>>(commandListString)

assert(resultList[0] as? NetCommand.AddEntity != null)
assert((resultList[0] as NetCommand.AddEntity).id == command.id)

Это терпит неудачу на линии:

val resultList = mapper.readValue<ArrayList<NetCommand>>(commandListString)

С этой ошибкой:

Missing type id when trying to resolve subtype of [simple type, class shared.NetCommand]: missing type id property 'type'
 at [Source: (String)"[{"id":1,"position":{"x":0,"y":0},"table":"KNIGHT"}]"; line: 1, column: 51] (through reference chain: java.util.ArrayList[0])
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class shared.NetCommand]: missing type id property 'type'
 at [Source: (String)"[{"id":1,"position":{"x":0,"y":0},"table":"KNIGHT"}]"; line: 1, column: 51] (through reference chain: java.util.ArrayList[0])

Любые идеи, почему мое поле типа не сериализуется?


(Меньше, чем идеал) Решение

Я нашел решение вручную добавить уже инициализированное поле в тело подкласса с именем подкласса. Например.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = AddEntity::class, name = "AddEntity"),
    JsonSubTypes.Type(value = RemoveEntity::class, name = "RemoveEntity"),
    JsonSubTypes.Type(value = MoveEntity::class, name = "MoveEntity"),
    JsonSubTypes.Type(value = SpeakEntity::class, name = "SpeakEntity"),
    JsonSubTypes.Type(value = AddItem::class, name = "AddItem")
)
sealed class NetCommand { val type: String = javaClass.simpleName }
class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
class RemoveEntity(val id: Long) : NetCommand()
class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
class AddItem(val id: Long, val item: Item) : NetCommand()

В идеале я хотел бы просто использовать простое имя класса автоматически, а не name = "AddEntity" и т.д. на каждом JsonSubTypes.Type вызов.

1 ответ

Решение

Я думаю, что нашел лучшее решение, которое я найду. С использованием JsonTypeInfo.Id.CLASS для отображения мне больше не нужно предоставлять имена для каждого подтипа - он просто полагается на полное имя класса. Это автоматически использует имя поля @class который я могу автоматически заполнить на суперкласс NetCommand с использованием @JsonProperty аннотация, чтобы правильно назвать поле. Также стоит отметить, что нам не нужно предоставлять @JsonSubTypes аннотация вообще.

Скорее бы использовать SimpleName (например, AddItem вместо my.fully.qualified.path.AddItem) но пока не понял

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
sealed class NetCommand { @JsonProperty("@class") val type = javaClass.canonicalName }
class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
class RemoveEntity(val id: Long) : NetCommand()
class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
class AddItem(val id: Long, val item: Item) : NetCommand()

В дополнение к решению OP и комментарию ryfterek следующая аннотация позаботилась бы о явном объявлении, упомянутом @JsonProperty("@class") val type = javaClass.canonicalName свойство:@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type"). Где "тип" - это имя поля, которое будет объявлено в POJO.

Другие вопросы по тегам