2

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.

Mohammed Idris
  • 768
  • 2
  • 9
  • 26

1 Answers1

1

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": {}
}
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
  • your solution eliminated the leaf - `"undefined": [ { "cropName": "Brinjal", "varietyName": null, "subVarietyName": null, "varietyId": null, "cropId": 8, "subVarietyId": null }` However, I do not wish to use `undefined` as the key. If the key is `null`, the code should `continue` to next loop. If the subVariety is null I want empty brackets. `{}` – Mohammed Idris Jul 18 '19 at 08:27
  • 1
    @MohammedIdris Yes you are right. forgot to deal with that. I updated my answer and added an extended solution, which removes the `null` values. – Samuel Philipp Jul 18 '19 at 14:40