Spring与Jackson版本迁移:Jackson 3无法反序列化父类final Map字段
问题
在企业项目从Spring 3.x迁移至Spring 4.x、Jackson 2.x迁移至Jackson 3.x的过程中,遇到Jackson 3无法识别content字段导致反序列化失败的问题——相同代码在Jackson 2中可正常运行。
示例代码(可在IDE JShell运行)
import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.json.JsonMapper; import java.io.Serializable; import java.util.*; abstract class BaseMessage { protected UUID id; protected final Map<String, Serializable> content; // 其他字段… protected BaseMessage() { id = UUID.randomUUID(); content = new HashMap<>(); } public <T extends Serializable> void addContent(final String key, final T value) { content.put(key, value); } public <T extends Serializable> void addContent(final Map<String, T> contentToAdd) { content.putAll(contentToAdd); } public <T extends Serializable> void overrideContent(final Map<String, T> newContent) { content.clear(); content.putAll(newContent); } public void setId(final UUID id) { this.id = id; } public UUID getId() { return id; } public Map<String, Serializable> getContent() { return content; } } class ClientMessage extends BaseMessage { private String upn; // 其他字段和方法… public void setUpn(final String upn) { this.upn = upn; } @Override public String toString() { return "ClientMessage{" + "upn='" + upn + "', id=" + id + ", content=" + content + '}'; } } final String jsonMessage = """ { "id": "00000000-0000-0000-0000-000000000000", "content": { "test": true, "prod": false, "demo": "Demonstration" }, "upn": "nemo@exemple.org" }"""; // 主动启用FAIL_ON_UNKNOWN_PROPERTIES以突出Jackson 3+的问题 JsonMapper.builder() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) // 添加该配置仅为了在错误信息中包含源位置,无实际解决作用 .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) .build() .readValue(jsonMessage, ClientMessage.class);
(注:原代码存在两处笔误:jdonMessage应为jsonMessage,enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION缺少闭合括号,已修正)
Jackson 3报错信息
Unrecognized property "content" (class ClientMessage), not marked as ignorable (2 known properties: "id", "upn")
at [Source: (String)"{
"id": "00000000-0000-0000-0000-000000000000",
"content": {
"test": true,
"prod": false,
"demo": "Demonstration"
},
"upn": "nemo@exemple.org"
}"; line: 3, column: 15] (through reference chain: ClientMessage["content"])
Jackson 2运行情况
Jackson 2中使用如下代码可正常反序列化:
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; // […] new ObjectMapper() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .readValue(jsonMessage, ClientMessage.class)
预期(及Jackson 2实际)结果:
[…] = ClientMessage{upn='nemo@exemple.org', id=00000000-0000-0000-0000-000000000000, content={test=true, prod=false, demo=Demonstration}}
解决方案
Jackson 3对JavaBean属性的识别规则做了严格调整,核心原因是:content字段是父类的final字段,且仅提供了getContent() getter方法,无对应的setContent() setter方法,Jackson 3默认不再将此类字段视为可反序列化的属性。
可通过以下几种方式修复:
1. 添加@JsonProperty注解标记content字段
直接在父类的content字段上添加tools.jackson.annotation.JsonProperty注解,明确告知Jackson该字段需要参与序列化/反序列化:
import tools.jackson.annotation.JsonProperty; abstract class BaseMessage { protected UUID id; @JsonProperty protected final Map<String, Serializable> content; // 其他代码不变… }
此方法最直接,无需修改现有方法结构。
2. 提供兼容的setContent方法
虽然content是final字段,但可以添加一个接收Map<String, Serializable>参数的setContent方法,内部调用overrideContent来更新内容:
abstract class BaseMessage { // 其他代码不变… public void setContent(Map<String, Serializable> newContent) { overrideContent(newContent); } }
Jackson 3会识别到setContent方法,将JSON中的content字段映射到该方法完成反序列化。
3. 配置Jackson启用兼容模式(不推荐)
如果不想修改实体类,可以通过配置Jackson的反序列化特性,允许通过getter方法对应的字段进行反序列化,但此方法可能引入其他兼容性问题,仅作为临时方案:
JsonMapper.builder() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(DeserializationFeature.USE_GETTERS_AS_SETTERS) // 启用getter作为setter的兼容模式 .build() .readValue(jsonMessage, ClientMessage.class);
注意:USE_GETTERS_AS_SETTERS是Jackson 3中新增的兼容特性,用于适配旧版本的行为,但长期来看建议优先使用前两种方法。
内容的提问来源于stack exchange,提问作者gblaise

