I have a List<CropDetailDto>
returned by Hibernate Projection of a native query in postgresql.
I want to represent this in a hierarchical / tree format to send it as response.
I used multiple nested Collectors.groupingBy
on List<CropDetailDto>
public Map<String, Map<String, Map<String, List<CropDetailDto>>>> findCropDetails() {
List<CropDetailDto> cropdetails = cropRepository.getCropDetails();
return cropdetails.stream().collect(Collectors.groupingBy(CropDetailDto::getCropName, Collectors.groupingBy(
v -> v.getVarietyName() == null ? "undefined" : v.getVarietyName(),
Collectors.groupingBy(m -> m.getSubVarietyName() == null ? "undefined" : m.getSubVarietyName()))));
}
I was somehow able to represent the data in this format.
{
"Tomato": {
"Red Tomato": {
"Red Tomato 1 quality": [
{
"cropName": "Tomato",
"varietyName": "Red Tomato",
"subVarietyName": "Red Tomato 1 quality",
"varietyId": 1002,
"cropId": 1,
"subVarietyId": 1003 //cropDetailDto is again represented
//at the leaf level
}
],
"Red Tomato 2 quality": [
{
"cropName": "Tomato",
"varietyName": "Red Tomato",
"subVarietyName": "Red Tomato 2 quality",
"varietyId": 1002,
"cropId": 1,
"subVarietyId": 1004
}
]
}
},
"Brinjal": {
"undefined": {
"undefined": [
{
"cropName": "Brinjal",
"varietyName": null,
"subVarietyName": null,
"varietyId": null,
"cropId": 8,
"subVarietyId": null
}
]
}
}
}
However, this representation has undefined
as the key, since it threw NPE for null keys.
I would like to represent the same data in a much cleaner format.
Something like this:
{
"Tomato": {
"Red Tomato": [
"Red Tomato 1 quality",
"Red Tomato 2 quality"
]
},
"Brinjal": {}
}
i.e., if there is a null key I would like it to move to next entry.
I recently started using java 8, I am looking for a concise way to achieve the above structure.
CropDetailDto:
public interface CropDetailDto {
Long getCropId();
String getCropName();
Long getVarietyId();
String getVarietyName();
Long getSubVarietyId();
String getSubVarietyName();
}
The resulting return type can also be a simple HashMap<String, Object>
. Also , if it is not possible to achieve this using Java 8 features, kindly provide a way to implement this without java 8.
Thanks in advance.
You can use Collectors.mapping()
instead of the last Collectors.groupingBy()
:
public Map<String, Map<String, List<String>>> findCropDetails() {
List<CropDetailDto> cropdetails = cropRepository.getCropDetails();
return cropdetails.stream().collect(Collectors.groupingBy(CropDetailDto::getCropName,
Collectors.groupingBy(v -> v.getVarietyName() == null ? "undefined" : v.getVarietyName(),
Collectors.mapping(m -> m.getSubVarietyName() == null ? "undefined" : m.getSubVarietyName(),
Collectors.toList()))));
}
This will return the following map:
{
"Tomato": {
"Red Tomato": [
"Red Tomato 1 quality",
"Red Tomato 2 quality"
]
},
"Brinjal": {
"undefined": [
"undefined"
]
}
}
To remove the null
values from the result you can use Collectors.filtering()
and modify the above code a bit:
public Map<String, Map<String, List<String>>> findCropDetails() {
List<CropDetailDto> cropdetails = cropRepository.getCropDetails();
return cropdetails.stream().collect(Collectors.groupingBy(CropDetailDto::getCropName,
Collectors.filtering(v -> v.getVarietyName() != null, Collectors.groupingBy(CropDetailDto::getVarietyName,
Collectors.filtering(m -> m.getSubVarietyName() != null, Collectors.mapping(CropDetailDto::getSubVarietyName,
Collectors.toList()))))));
}
Using this the final result will look like this:
{
"Tomato": {
"Red Tomato": [
"Red Tomato 1 quality",
"Red Tomato 2 quality"
]
},
"Brinjal": {}
}