使用 Jackson 在 Kotlin 中缺少具有多态(反)序列化的身份字段
Missing identity field with polymorphic (de)serialisation in Kotlin with Jackson
我有以下 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) 传递给第二个应用程序,并将它们正确反序列化为适当的子 class.
我还编写了一个简单的测试来帮助我迭代 annotations/jackson 映射器的不同配置:
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])
知道为什么我的类型字段没有被序列化吗?
(不太理想)解决方案
我找到了一个解决方案,将一个已经初始化的字段手动添加到 subclasses 的正文中,并使用 subclass 的名称。例如
@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()
理想情况下,我只想自动使用简单的 class 名称,而不是在每次 JsonSubTypes.Type
调用时使用 name = "AddEntity"
等。
我想我已经找到了我将要找到的最佳解决方案。使用 JsonTypeInfo.Id.CLASS
进行映射,我不再需要为每个子类型提供名称 - 它只依赖于完全限定的 class 名称。这会自动使用字段名称 @class
,我可以使用 @JsonProperty
注释自动将其填充到超级 class NetCommand
上以正确命名字段。另外值得注意的是,我们根本不需要提供 @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")
。
其中 'type' 是将在 POJO 中声明的字段的名称。
我有以下 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) 传递给第二个应用程序,并将它们正确反序列化为适当的子 class.
我还编写了一个简单的测试来帮助我迭代 annotations/jackson 映射器的不同配置:
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])
知道为什么我的类型字段没有被序列化吗?
(不太理想)解决方案
我找到了一个解决方案,将一个已经初始化的字段手动添加到 subclasses 的正文中,并使用 subclass 的名称。例如
@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()
理想情况下,我只想自动使用简单的 class 名称,而不是在每次 JsonSubTypes.Type
调用时使用 name = "AddEntity"
等。
我想我已经找到了我将要找到的最佳解决方案。使用 JsonTypeInfo.Id.CLASS
进行映射,我不再需要为每个子类型提供名称 - 它只依赖于完全限定的 class 名称。这会自动使用字段名称 @class
,我可以使用 @JsonProperty
注释自动将其填充到超级 class NetCommand
上以正确命名字段。另外值得注意的是,我们根本不需要提供 @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")
。
其中 'type' 是将在 POJO 中声明的字段的名称。