You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

@JsonTypeInfo在REST端点使用具体类时的序列化异常咨询

问题原因与解决方案

你遇到的这个问题是Jackson注解继承特性导致的预期行为,不是操作错误。让我一步步拆解原因和解决办法:

为什么直接用Message1会要求@class属性?

你在Message接口上添加了@JsonTypeInfo注解,而Jackson会将接口上的序列化/反序列化配置继承给所有实现类。这意味着:

  • 当你处理List<Message>时,Jackson会按照注解要求给每个子类加上@class字段,这符合你的预期;
  • 但当你直接使用Message1作为请求体时,Jackson依然会应用接口上的@JsonTypeInfo规则,期望输入JSON中包含@class字段来识别子类型——哪怕你已经明确指定了参数类型是Message1,它还是会触发多态类型解析逻辑,所以才会抛出"Missing type id property '@class'"的异常。

解决办法

根据你的场景,有几种实用的解决方案:

方案1:将@JsonTypeInfo移到抽象类而非接口

把原来的Message接口改成抽象类,将类型信息注解放在抽象类上:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
abstract class Message : Serializable

data class Message1(val name: String, val age: Int) : Message()
data class Message2(val name: String, val nickName: String) : Message()

这样做的好处是:

  • 当你处理List<Message>(抽象类类型)时,Jackson会触发多态类型解析,自动添加/识别@class字段;
  • 当你直接使用Message1作为请求体时,Jackson知道这是具体类型,不会触发多态逻辑,也就不需要@class字段了。

方案2:在具体子类上用@JsonIgnoreTypeInfo覆盖接口配置

如果你必须保留Message作为接口,可以在不需要类型信息的子类上添加@JsonIgnoreTypeInfo注解,覆盖接口的配置:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
interface Message : Serializable

@JsonIgnoreTypeInfo
data class Message1(val name: String, val age: Int) : Message

@JsonIgnoreTypeInfo
data class Message2(val name: String, val nickName: String) : Message

这个注解会告诉Jackson:当直接处理该子类时,忽略接口上的@JsonTypeInfo规则,不需要@class字段。而当你处理List<Message>时,依然会正常生成@class字段用于反序列化。

方案3:使用Mix-in灵活配置不同场景的规则

如果你的场景更复杂(比如部分子类需要在某些场景保留类型信息),可以用Jackson的Mix-in功能来给不同端点配置不同的序列化规则:

  1. 定义一个不带@JsonTypeInfo的Mix-in类:
interface MessageWithoutTypeInfo : Serializable
  1. 在你的MessageController中,针对/add1端点的ObjectMapper注册Mix-in:
@RestController
class MessageController {
    @Autowired
    private lateinit var objectMapper: ObjectMapper

    @PostMapping("/add1")
    fun add(@RequestBody content: Message1) {
        // 临时注册Mix-in,处理Message1时忽略类型信息
        val localMapper = objectMapper.copy()
        localMapper.addMixIn(Message::class.java, MessageWithoutTypeInfo::class.java)
        // 用localMapper处理请求内容
        // do something
    }

    @GetMapping("/messages")
    fun getEndpoints(): List<Message> {
        return listOf(Message1("Marco", 22), Message2("Polo", "Poli"))
    }
}

这种方式更灵活,注意使用ObjectMapper的副本避免影响全局配置。

总结

你的问题本质是Jackson对接口注解的继承特性导致的,属于预期行为。通过调整注解位置、覆盖配置或使用Mix-in,都可以解决"具体类请求体要求@class字段"的问题,推荐优先使用方案1或方案2,实现起来更简洁。

内容的提问来源于stack exchange,提问作者peach

火山引擎 最新活动