Java Stream to aggregate list of JSONs into groups

Viewed 228

I have list of objects in java, like this.

[
  {
    "applicationNumber": "100400",
    "users": "A",
    "category": "student"
  },
  {
    "applicationNumber": "100400",
    "users":"B",
    "category": "student"
  },
  {
    "applicationNumber": "100400",
    "users":"C",
    "category": "neighbour"
  },
  {
    "applicationNumber": "100400",
    "users": "D",
    "category": "neighbour"
  },
  {
    "applicationNumber": "200543",
    "users": "C",
    "category": "student"
  },
  {
    "applicationNumber": "200543",
    "users": "A",
    "category": "student"
  },
  {
    "applicationNumber": "200543",
    "users":"D",
    "category": "friend"
  }
]

I want to group users as list (order does not matter) for each category for every applicationNumber. Can refer below json to get the idea.

[
  {
    "applicationNumber": "100400",
    "users": [
      "A",
      "B"
    ],
    "category": "student"
  },
  {
    "applicationNumber": "100400",
    "users": [
      "C",
      "D"
    ],
    "category": "neighbour"
  },
  {
    "applicationNumber": "200543",
    "users": [
      "C",
      "A"
    ],
    "category": "student"
  },
  {
    "applicationNumber": "200543",
    "users": [
      "D"
    ],
    "category": "friend"
  }
]

I am able to this using a for loop, HashMap and if else conditions. I want to use Java 8 stream to achieve the same . Can anyone help me , I am new to java.

PS: Thank you in advance

1 Answers

I think using streams here is a little bit overengineering but you can to that in two steps. First you need to use Collectors.groupingBy() to group yours pojos into map of lists. Next you need to reduce each list to a single value by using stream().reduce().

ObjectMapper mapper = new ObjectMapper()
        .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
List<Application> applications = Arrays.asList(mapper.readValue(json, Application[].class));
List<Application> groupedApplications = applications.stream()
        .collect(Collectors.groupingBy(ApplicationKey::of, Collectors.toList()))
        .values().stream()
        .map(apps -> apps.stream().reduce(Application::merge))
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());

Application.java:

public class Application {
    private String applicationNumber;
    private String category;
    private List<String> users = new ArrayList<>();

    public static Application merge(Application first, Application second) {
        assert ApplicationKey.of(first).equals(ApplicationKey.of(second));
        Application merged = new Application(first.applicationNumber, first.category, first.getUsers());
        merged.users.addAll(second.getUsers());
        return merged;
    }
    //constructor, getters, setters
}

ApplicationKey.java

public class ApplicationKey {
    private String applicationNumber;
    private String category;

    public static ApplicationKey of(Application application) {
        return new ApplicationKey(application.getApplicationNumber(), application.getCategory());
    }

    public ApplicationKey(String applicationNumber, String category) {
        this.applicationNumber = applicationNumber;
        this.category = category;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ApplicationKey that = (ApplicationKey) o;
        return Objects.equals(applicationNumber, that.applicationNumber) &&
                Objects.equals(category, that.category);
    }

    @Override
    public int hashCode() {
        return Objects.hash(applicationNumber, category);
    }

    //getters, setters
}
Related