You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson 2.13.4.2泛型MyMessage反序列化问题咨询

问题背景

我定义了带泛型M的MyMessage类,泛型M有多种实现(比如ContentType1):

// 消息定义
@Value
@Builder(toBuilder = true)
@Jacksonized
public class MyMessage<M> {
    @Builder.Default
    Map<String, String> props = new HashMap<>();

    M content;
}

// MyMessage中泛型M的一种实现
class ContentType1 {
    String name;
    SomeSimplePojo pojo;
    Map<String, String> contentProps;
}

对应的JSON示例:

{
    "props": {
        "trace-id": "3468f6022b749dbc"
    },
    "content": {
        "name": "contentExample1",
        "pojo": {
            "field1": "val1",
            "field2": "val2"
        },
        "contentProps": {
            "/Count": "9",
            "/Email": "someone@stackoverflow.com"
        }
    }
}

之前使用Jackson 2.10.4版本时,以下反序列化代码运行正常:

ObjectMapper objectMapper = new ObjectMapper();
MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {});

// clazzOfM是M类型的Class对象
MyMessage<M> convertedMsg = MyMessage.<M>builder()
    .content(objectMapper.convertValue(rawMsg.getContent(), clazzOfM))
    .props(rawMsg.getProps())
    .build();

但升级Jackson到2.13.4.2(其他Jackson组件同步到2.13.4)后,MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {});这行抛出MismatchedInputException,异常信息如下:

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: (String)"{"props":{"trace-id":"3468f6022b749dbc"},"content":{"name":"contentExample1","pojo":{"field1":"val1","field2":"val2"},"contentProps":{"/Count":"9","/Email":"someone@stackoverflow.com"}}}"; line: 1, column: 52] (through reference chain: com.demo.example.MyMessage$MyMessageBuilder["content"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741)
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1515)
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1420)
        at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:932)
        at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62)
        at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
        at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
        at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:293)
        at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:217)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3612)

我尝试将该行修改为MyMessage<M> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<M>>() {});,此时不再抛异常,但有两个疑问:

  1. 这个修改是Jackson 2.13.4.2下反序列化MyMessage的正确方案吗?如果不是,这类泛型反序列化的最佳实践是什么?
  2. 修改后,rawMsg.content的类型是LinkedHashMap而非预期的M(测试中是ContentType1),但通过objectMapper.convertValue(rawMsg.getContent(), clazzOfM)转换后,convertedMsg.content的类型变成了ContentType1,这是为什么?
问题解答

1. 修改方案的正确性与泛型反序列化最佳实践

你当前的修改不是最优方案,本质上是让Jackson把content反序列化为默认的LinkedHashMap(因为泛型类型M在运行时被擦除,Jackson无法确定具体类型),后续再手动转换。这种方式可行,但存在额外的转换开销,也不够优雅。

正确的泛型反序列化方案

直接构造包含具体M类型的TypeReference,一步完成反序列化,无需中间转换:

// 假设clazzOfM是ContentType1.class
TypeReference<MyMessage<ContentType1>> typeRef = new TypeReference<MyMessage<ContentType1>>() {};
MyMessage<ContentType1> msg = objectMapper.readValue(jsonStr, typeRef);

如果M的类型是动态的(无法提前硬编码),可以通过TypeFactory动态构造泛型类型:

JavaType messageType = objectMapper.getTypeFactory()
    .constructParametricType(MyMessage.class, clazzOfM);
MyMessage<M> msg = objectMapper.readValue(jsonStr, messageType);

为什么旧版本Jackson能运行?

Jackson 2.10.x版本中,对于String类型的反序列化,会尝试将JSON对象序列化为字符串形式(比如"{...}"),但这种行为不符合规范,后续版本(2.13.x)修复了这个问题,严格校验类型匹配——JSON对象无法直接反序列化为String,因此抛出异常。你的旧代码本质上是依赖了Jackson的一个非标准兼容行为,升级后被修复导致报错。

2. rawMsg.content为LinkedHashMap的原因

这是Java泛型类型擦除和Jackson的默认反序列化规则共同导致的:

  • 运行时MyMessage<M>的泛型参数M会被擦除,Jackson无法通过反射获取到M的具体类型。
  • 当Jackson遇到无法确定具体类型的JSON对象时,默认会将其反序列化为LinkedHashMap(键值对结构的默认映射类型)。

而objectMapper.convertValue(rawMsg.getContent(), clazzOfM)能成功转换,是因为此时你明确告诉了Jackson目标类型clazzOfM,它可以将LinkedHashMap中的键值对映射到ContentType1的字段上,完成类型转换。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 01:01:18