Home > Software design >  Include field in response with hierarchical relationship in the same table
Include field in response with hierarchical relationship in the same table

Time:08-09

I need to generate a response with the parentRoot field included, but this field has a relationship with the same entity and is annotated with @JsonIgnore to avoid the StackOverflowError: Infinite recursion. Here's the answer I get with the current implementation and the answer I hope to get with your help!

Current response:

[
    {
        "id": 1,
        "full_name": "William",
        "children": [
            {
                "id": 2,
                "full_name": "Henry",
                "children": [
                    {
                        "id": 3,
                        "full_name": "Matt",
                        "children": [
                            {
                                "id": 7,
                                "full_name": "Sophi",
                                "children": []
                            }
                        ]
                    },
                    {
                        "id": 4,
                        "full_name": "Alisa",
                        "children": [
                            {
                                "id": 6,
                                "full_name": "Alexa",
                                "children": []
                            }
                        ]
                    }
                ]
            },
            {
                "id": 5,
                "full_name": "May",
                "children": []
            }
        ]
    },
    {
        "id": 8,
        "full_name": "Olivia",
        "children": [
            {
                "id": 9,
                "full_name": "John",
                "children": [
                    {
                        "id": 11,
                        "full_name": "Oliver",
                        "children": []
                    },
                    {
                        "id": 12,
                        "full_name": "Mia",
                        "children": []
                    }
                ]
            },
            {
                "id": 10,
                "full_name": "Mary",
                "children": [
                    {
                        "id": 13,
                        "full_name": "Evelyn",
                        "children": []
                    }
                ]
            }
        ]
    }
]

Expected response:

[
    {
        "id": 1,
        "full_name": "William",
        "root_parent_id": null,
        "children": [
            {
                "id": 2,
                "full_name": "Henry",
                "root_parent_id": 1,
                "children": [
                    {
                        "id": 3,
                        "full_name": "Matt",
                        "root_parent_id": 1,
                        "children": [
                            {
                                "id": 7,
                                "full_name": "Sophi",
                                "root_parent_id": 1,
                                "children": []
                            }
                        ]
                    },
                    {
                        "id": 4,
                        "full_name": "Alisa",
                        "root_parent_id": 1,
                        "children": [
                            {
                                "id": 6,
                                "full_name": "Alexa",
                                "root_parent_id": 1,
                                "children": []
                            }
                        ]
                    }
                ]
            },
            {
                "id": 5,
                "full_name": "May",
                "root_parent_id": 1,
                "children": []
            }
        ]
    },
    {
        "id": 8,
        "full_name": "Olivia",
        "root_parent_id": null,
        "children": [
            {
                "id": 9,
                "full_name": "John",
                "root_parent_id": 8,
                "children": [
                    {
                        "id": 11,
                        "full_name": "Oliver",
                        "root_parent_id": 8,
                        "children": []
                    },
                    {
                        "id": 12,
                        "full_name": "Mia",
                        "root_parent_id": 8,
                        "children": []
                    }
                ]
            },
            {
                "id": 10,
                "full_name": "Mary",
                "root_parent_id": 8,
                "children": [
                    {
                        "id": 13,
                        "full_name": "Evelyn",
                        "root_parent_id": 8,
                        "children": []
                    }
                ]
            }
        ]
    }
]

Can you give me a hint on how I can implement this?

My classes:

MODEL

@Entity
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"hibernate_lazy_initializer", "handler"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {

    @Id
    @Getter
    @Setter
    @EqualsAndHashCode.Include
    private Long id;

    @Getter
    @Setter
    private String fullName;

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person parent;

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person rootParent;

    @Transient
    @Getter
    @Setter
    public List<Person> children = new ArrayList<>();

}

REPOSITORY

@Repository
public interface PersonRepo extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p "
              " WHERE p.parent.id IS NULL")
    List<Person> findRoots();

    @Query("SELECT p FROM Person p"
              " WHERE p.rootParent.id IN :rootIds ")
    List<Person> findChildrenInRoots(@Param("rootIds") List<Long> rootIds);

}

CONTROLLER

@RestController
@RequestMapping("/api/v1/person")
public class PersonController {

    @Autowired
    private PersonRepo personRepo;

    @GetMapping
    @Transactional(readOnly = true)
    public List<Person> getChildren() {
        List<Person> rootCategories = personRepo.findRoots();

        List<Long> rootCategoryIds = rootCategories.stream().map(Person::getId).collect(Collectors.toList());

        List<Person> children = personRepo.findChildrenInRoots(rootCategoryIds);

        children.forEach(subCategory -> {
            subCategory.getParent().getChildren().add(subCategory);
        });

        return rootCategories;
    }

}

DATA SQL

insert into PERSON values (1,'William',null, null);
insert into PERSON values (2,'Henry',1,1);
insert into PERSON values (3,'Matt',2,1);
insert into PERSON values (4,'Alisa',2,1);
insert into PERSON values (5,'May',1,1);
insert into PERSON values (6,'Alexa',4,1);
insert into PERSON values (7,'Sophi',3,1);
insert into PERSON values (8,'Olivia',null,null);
insert into PERSON values (9,'John',8,8);
insert into PERSON values (10,'Mary',8,8);
insert into PERSON values (11,'Oliver',9,8);
insert into PERSON values (12,'Mia',9,8);
insert into PERSON values (13,'Evelyn',10,8);

CodePudding user response:

If you don't want to add an extra field to your model for root information then just add the following method to your Person entity:

This will recursively call the parent to reach the root.

public String getRootParentId() {
    if (parent != null) {
        return parent.getRootParentId();
    } else if (children != null && !children.isEmpty()) {
        return name;
    } else return null;
}

Jackson will treat this method as a field in the serialization phase. and gives the result as you desired

  • Related