Jackson无法创建已注册类实例求助:@JsonIdentityInfo致反序列化失败
嘿,我之前也踩过Jackson这个序列化引用的坑!结合你说的场景和可复现示例,咱们来好好聊聊这个问题:
首先先把你提到的可复现示例补全(方便大家理解场景):
示例代码
子类 Child(带@JsonIdentityInfo注解)
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Child { private Long id; private String name; // 空构造器(Jackson反序列化必需) public Child() {} public Child(Long id, String name) { this.id = id; this.name = name; } // 省略getter、setter方法 }
父类 Parent
import java.util.ArrayList; import java.util.List; public class Parent { private String parentName; private List<Child> children = new ArrayList<>(); public Parent() {} public Parent(String parentName) { this.parentName = parentName; } public void addChild(Child child) { this.children.add(child); } // 省略getter、setter方法 }
测试代码(复现错误)
import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonReferenceTest { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); // 创建同一个子对象,重复添加到父对象的集合中 Child repeatedChild = new Child(1L, "MyChild"); Parent parent = new Parent("MyParent"); parent.addChild(repeatedChild); parent.addChild(repeatedChild); // 序列化正常,输出会是:{"parentName":"MyParent","children":[{"id":1,"name":"MyChild"},1]} String serializedJson = mapper.writeValueAsString(parent); System.out.println("序列化结果:" + serializedJson); // 这里反序列化会抛出异常! Parent deserializedParent = mapper.readValue(serializedJson, Parent.class); } }
问题根源
你猜的没错,这确实是Jackson 2.9.x版本的已知Bug!当使用@JsonIdentityInfo结合PropertyGenerator(依赖对象自身的ID属性)时,Jackson在反序列化集合中的重复ID引用时,无法正确关联到之前已经反序列化的子对象,导致解析失败。而移除注解后,Jackson会把每个ID对应的内容当成新对象来反序列化(即使是同一个对象的引用),所以不会报错,但这就失去了你原本想要的“只序列化一次子对象”的效果。
可行的解决方案
1. 升级Jackson版本(最推荐)
这个Bug在Jackson 2.10.0及以上版本已经被官方修复了,只要把你的Jackson依赖升级到2.10+,不需要修改任何代码,就能正常完成序列化和反序列化。
2. 更换ID生成器(无法升级版本时用)
如果暂时没办法升级版本,可以把@JsonIdentityInfo的生成器换成IntSequenceGenerator,让Jackson自动生成序列ID,而不是依赖对象自身的id属性,在2.9.x版本中这种方式的反序列化兼容性更好:
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id") public class Child { // 原有代码不变 }
序列化后的JSON会变成:{"parentName":"MyParent","children":[{"@id":1,"id":1,"name":"MyChild"},1]},这样反序列化就能正常识别引用了。
3. 自定义序列化/反序列化器(最繁琐)
如果以上两种方式都不适用,可以自己写自定义序列化器,手动控制子对象的序列化逻辑(第一次输出完整对象,后续输出ID),同时写对应的反序列化器来处理ID到对象的映射。不过这种方式需要编写较多代码,除非特殊场景,不推荐使用。
内容的提问来源于stack exchange,提问作者Benedikt Gansinger

