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

如何正确使用@JsonIdentityInfo解决Jackson序列化循环依赖问题

解决Java类循环依赖的Jackson序列化问题

你的三个类存在双向关联:Process ↔ User、Process ↔ AnsweredQuestionnaire,这会导致Jackson序列化时无限递归,最终抛出栈溢出异常。@JsonIdentityInfo是解决这类问题的核心方案,下面是正确的用法和修改后的代码:

核心原理

@JsonIdentityInfo的作用是给每个对象分配唯一标识:第一次序列化对象时输出完整结构,后续再遇到同一个对象时,只输出它的标识ID,避免重复遍历循环关联的对象。

因为你的类已经有自己的UUID主键(比如processId、userId),优先使用对象自身的主键作为标识,比Jackson自动生成的UUID更直观,也更符合业务逻辑。

修改后的代码实现

Process.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
// 使用自身的processId作为唯一标识
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "processId")
public class Process {

    private UUID processId;

    private List<User> users = new ArrayList<>();

    private List<UnitType> units = new ArrayList<>();

    private String furtherComment;

    private List<AnsweredQuestionnaire> answeredQuestionnaires = new ArrayList<>();


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Process)) return false;
        Process process = (Process) o;
        return getProcessId().equals(process.getProcessId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getProcessId());
    }
}

AnsweredQuestionnaire.java

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
// 使用自身的answeredQuestionnaireId作为唯一标识
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "answeredQuestionnaireId")
public class AnsweredQuestionnaire {

    private UUID answeredQuestionnaireId;

    private Questionnaire questionnaire;

    private Process process;

    public void addProcessToAnsweredQuestionnaire(Process process){
        //remove old association
        if(this.process != null){
            this.process.getAnsweredQuestionnaires().remove(this);
        }
        this.process = process;

        //add new association
        if(process != null){
            this.process.getAnsweredQuestionnaires().add(this);
        }
    } 
}

User.java

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
// 使用自身的userId作为唯一标识
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
public class User {

    private UUID userId;

    private String firstName;

    private String lastName;

    private String phoneNumber;

    private String email;

    private List<Role> roles = new ArrayList<>();

    private List<Process> processes = new ArrayList<>();


    public void addProcessToUser(Process process){
        this.processes.add(process);
        process.getUsers().add(this);
    }

    public void removeProcessFromUser(Process process){
        this.processes.remove(process);
        process.getUsers().remove(this);
    }

}

效果说明

当你序列化一个包含关联关系的对象时,比如:

// 创建测试对象
User user = new User(UUID.randomUUID(), "John", "Doe", "123456", "john@example.com", new ArrayList<>(), new ArrayList<>());
Process process = new Process(UUID.randomUUID(), new ArrayList<>(), new ArrayList<>(), "Test comment", new ArrayList<>());
AnsweredQuestionnaire aq = new AnsweredQuestionnaire(UUID.randomUUID(), null, null);

// 建立关联
user.addProcessToUser(process);
aq.addProcessToAnsweredQuestionnaire(process);

// 序列化
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(process);

输出的JSON会是这样的结构:

  • 第一次出现Process时,输出完整的字段,包括它的users和answeredQuestionnaires列表
  • 列表中的User和AnsweredQuestionnaire会输出完整结构,而它们内部引用的Process只会输出processId的值,不会重复序列化整个Process对象

这样就彻底解决了循环依赖导致的序列化栈溢出问题,同时反序列化时Jackson也能根据ID正确重建对象间的关联关系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 23:05:27