Issue
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);
Solution
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
Answered By - Zabihullah Alipour
Answer Checked By - Senaida (JavaFixing Volunteer)