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>>() {});,此时不再抛异常,但有两个疑问:
- 这个修改是Jackson 2.13.4.2下反序列化
MyMessage的正确方案吗?如果不是,这类泛型反序列化的最佳实践是什么? - 修改后,
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

