Jackson序列化Organization类时如何避免递归且返回一次parent字段?
解决组织实体递归序列化问题,同时返回一级Parent对象
针对你遇到的问题——加@JsonBackReference拿不到parent字段,去掉就触发无限递归,@JsonIdentityInfo又只能返回ID没法构建节点树,给你几个可行的解决方案:
方案1:用@JsonView区分序列化视图
这种方式能灵活控制不同场景下的序列化字段,既能返回当前实体的parent,又不让parent的parent被序列化:
- 先定义两个视图接口:
public interface BaseView {} public interface WithParentView extends BaseView {}
- 修改Organization实体类的字段注解:
@Getter @Setter @ToString(callSuper = true) @Entity(name = "organizations") public class Organization extends BaseEntity { // 仅在WithParentView视图下序列化parent,同时忽略parent自身的parent字段避免递归 @ManyToOne @JoinColumn(name = "parent_id", referencedColumnName = "id") @OnDelete(action = OnDeleteAction.CASCADE) @JsonView(WithParentView.class) @JsonIgnoreProperties("parent") private Organization parent; // 基础字段绑定BaseView,确保所有视图都能序列化这些内容 @JsonView(BaseView.class) @ManyToOne @JoinColumn(name = "organization_type_id", referencedColumnName = "id", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) @NotNull(message = "organizationType" + AppConstant.NOT_NULL_MESSAGE) private OrganizationType organizationType; @JsonView(BaseView.class) @Column(name = "address_id", nullable = false) @NotNull(message = "addressId" + AppConstant.NOT_NULL_MESSAGE) @Min(value = AppConstant.MIN_ID_VALUE, message = "addressId" + AppConstant.MIN_ID_MESSAGE) private Integer addressId; @JsonView(BaseView.class) @Column(name = "name", nullable = false) @NotNull(message = "name" + AppConstant.NOT_NULL_MESSAGE) @NotEmpty(message = "name" + AppConstant.NOT_EMPTY_MESSAGE) @Size(max = AppConstant.MAX_STRING_FIELD_LENGTH, message = "name" + AppConstant.STRING_FIELD_SIZE_MESSAGE) private String name; @JsonView(BaseView.class) @Column(name = "phone_number") @Size(max = AppConstant.MAX_STRING_FIELD_LENGTH, message = "phoneNumber" + AppConstant.STRING_FIELD_SIZE_MESSAGE) private String phoneNumber; @JsonView(BaseView.class) @OneToMany(mappedBy = "organization", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("organization-organizationApps") private List<OrganizationApp> organizationApps; }
- 在接口方法上指定使用WithParentView视图:
@GetMapping("/{id}") @JsonView(WithParentView.class) public ResponseEntity<Organization> getOrganization(@PathVariable Long id) { Organization org = organizationService.findById(id); return ResponseEntity.ok(org); }
方案2:自定义DTO(最稳妥的方式)
直接创建数据传输对象,完全控制返回的字段结构,从根源避免递归:
- 创建OrganizationDTO:
@Getter @Setter public class OrganizationDTO { private Long id; private String name; private String phoneNumber; private Integer addressId; private OrganizationType organizationType; private List<OrganizationApp> organizationApps; // 仅包含parent的基础信息,不嵌套parent的parent private ParentOrganizationDTO parent; // 内部类定义parent的精简DTO @Getter @Setter public static class ParentOrganizationDTO { private Long id; private String name; // 按需添加parent的其他必要字段,不要加parent字段 } }
- 在业务层实现实体转DTO:
public OrganizationDTO convertToDTO(Organization org) { OrganizationDTO dto = new OrganizationDTO(); dto.setId(org.getId()); dto.setName(org.getName()); dto.setPhoneNumber(org.getPhoneNumber()); dto.setAddressId(org.getAddressId()); dto.setOrganizationType(org.getOrganizationType()); dto.setOrganizationApps(org.getOrganizationApps()); if (org.getParent() != null) { OrganizationDTO.ParentOrganizationDTO parentDto = new OrganizationDTO.ParentOrganizationDTO(); parentDto.setId(org.getParent().getId()); parentDto.setName(org.getParent().getName()); // 按需设置parent的其他字段 dto.setParent(parentDto); } return dto; }
- 接口直接返回DTO:
@GetMapping("/{id}") public ResponseEntity<OrganizationDTO> getOrganization(@PathVariable Long id) { Organization org = organizationService.findById(id); OrganizationDTO dto = organizationService.convertToDTO(org); return ResponseEntity.ok(dto); }
方案3:用@JsonIgnoreProperties忽略parent的递归字段
在parent字段上直接忽略它的parent属性,序列化当前实体时,parent对象不会包含自己的parent,避免无限递归:
修改Organization实体的parent字段:
@ManyToOne @JoinColumn(name = "parent_id", referencedColumnName = "id") @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnoreProperties("parent") // 忽略parent对象中的parent字段 private Organization parent;
这种方式最简单,但灵活性稍差,适合只需要一级parent的场景。
方案4:自定义Json序列化器
如果需要更精细的控制,可以写一个自定义序列化器:
- 创建自定义序列化器:
public class OrganizationSerializer extends StdSerializer<Organization> { public OrganizationSerializer() { this(null); } public OrganizationSerializer(Class<Organization> t) { super(t); } @Override public void serialize(Organization org, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); // 序列化基础字段 gen.writeNumberField("id", org.getId()); gen.writeStringField("name", org.getName()); gen.writeStringField("phoneNumber", org.getPhoneNumber()); gen.writeNumberField("addressId", org.getAddressId()); gen.writeObjectField("organizationType", org.getOrganizationType()); gen.writeObjectField("organizationApps", org.getOrganizationApps()); // 序列化parent,但只输出基础信息,不包含parent的parent if (org.getParent() != null) { gen.writeFieldName("parent"); gen.writeStartObject(); gen.writeNumberField("id", org.getParent().getId()); gen.writeStringField("name", org.getParent().getName()); // 按需添加parent的其他字段 gen.writeEndObject(); } gen.writeEndObject(); } }
- 在实体类上指定使用这个序列化器:
@JsonSerialize(using = OrganizationSerializer.class) @Getter @Setter @ToString(callSuper = true) @Entity(name = "organizations") public class Organization extends BaseEntity { // 原字段定义不变,去掉之前的JsonBackReference等注解 }
内容的提问来源于stack exchange,提问作者Muhammad Zohaib
相关产品推荐
相关产品推荐

