Załóżmy że mamy dane jak poniżej. Dwie pierwsze kolumny to grupa, trzy kolejne kolumny dotyczą użytkownika, a ostatnie dwie zadania.
Grupa może posiadać wielu użytkowników. Użytkownik może posiadać wiele zadań.
Dane są spłaszczone i nie mamy na to wpływu.
Dane są dodatkowo posortowane po ID grupy, ID użytkownika, ID zadania.
List<DataRow> rows = new ArrayList<>();
//new DataRow([group id], [group name], [user id], [user name], [user surname], [task id], [task name])
rows.add(new DataRow(1, "Group 1", 1, "Jon", "Snow", 1, "Task 1"));
rows.add(new DataRow(1, "Group 1", 2, "Harry", "Potter", 2, "Task 2"));
rows.add(new DataRow(1, "Group 1", 2, "Harry", "Potter", 3, "Task 3"));
rows.add(new DataRow(2, "Group 2", 3, "Lara", "Croft", 4, "Task 4"));
rows.add(new DataRow(2, "Group 2", 3, "Lara", "Croft", 5, "Task 5"));
Zastanawiam się jak najładniej i najlepiej przepisać dane wejściowe do takiej struktury (bez używania żadnych bibliotek):
public class GroupDto {
private Integer id;
private String name;
private List<UserDto> users;
...
}
public class UserDto {
private Integer id;
private String name;
private String surname;
private List<TaskDto> tasks;
...
}
public class TaskDto {
private Integer id;
private String name;
...
}
Znam kilka sposobów na rozwiązanie tego, jednak nie mam pojęcia jaki sposób obecnie jest uważany za zgodny z zasadami pisania w Javie.
Rozwiązanie 1: chyba nie wygląda to najładniej?
List<GroupDto> result = new ArrayList<>();
GroupDto lastGroup = null;
UserDto lastUser = null;
for (DataRow row : rows) {
if (lastGroup == null || !lastGroup.getId().equals(row.getGroupId())) {
lastGroup = new GroupDto(row.getGroupId(), row.getGroupName());
result.add(lastGroup);
}
if (lastUser == null || !lastUser.getId().equals(row.getUserId())) {
lastUser = new UserDto(row.getUserId(), row.getUserName(), row.getUserSurname());
lastGroup.getUsers().add(lastUser);
}
lastUser.getTasks().add(new TaskDto(row.getTaskId(), row.getTaskName()));
}
Rozwiązanie 2: można pokusić się o użycie strumieni (wygląda ładniej). DTO powinny mieć zaimplementowane metody equals i hashCode.
Minus tego rozwiązania jest taki, że dla każdego wiersza danych wejściowych tworzymy trzy obiekty, a cześć z nich i tak jest odfiltrowana przy wkładaniu do mapy.
Map<GroupDto, Map<UserDto, List<TaskDto>>> result = rows.stream().collect(
groupingBy(row -> new GroupDto(row.getGroupId(), row.getGroupName()),
groupingBy(row -> new UserDto(row.getUserId(), row.getUserName(), row.getUserSurname()),
mapping(row -> new TaskDto(row.getTaskId(), row.getTaskName()), toList()))));
Przy tym rozwiązaniu pewnie można pokusić się o zostawienie wszystkiego w mapach lub połączenie wszystkiego tak:
result .forEach((group, users) -> {
group.getUsers().addAll(users.keySet());
users.forEach((user, tasks) -> {
user.getTasks().addAll(tasks);
});
});
return result.keySet().stream().collect(toList());
Można to napisać jeszcze na inne sposoby, ale chyba już pokazałem o co mi mniej więcej chodzi.
Pytanie: jaki sposób jest najlepszy na przepisanie płaskich danych do przedstawionej struktury dto? Zależy mi na tym żeby było czytelne dla jak największej liczby osób i żeby nikt nie narzekał na jakość kodu :)