杰克逊(Jackson)在科特林(Kotlin)中缺少具有多态(反)序列化的身份字段

时间:2018-11-04 00:53:23

标签: json kotlin jackson polymorphism sealed-class

我具有以下注释的类层次结构:

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

我的想法是我可以将NetCommand的集合(ArrayList)传递给第二个应用程序,然后将它们正确反序列化为适当的子类。

我还编写了一个简单的测试来帮助我迭代批注/杰克逊映射器的不同配置:

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等。

2 个答案:

答案 0 :(得分:2)

我想我已经找到了最好的解决方案。使用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()

答案 1 :(得分:1)

作为OP解决方案和ryfterek注释的补充,以下注释将用于显式声明提及的@JsonProperty("@class") val type = javaClass.canonicalName属性: @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")。 其中“类型”是将在POJO中声明的字段的名称。