Issue
I have a list of objects retrieved through an external REST service that I'd like to sort with user defined values in the Pageable.Sort property.
This is my DTO object:
@Data
@Builder
public class UserDto {
private String guid;
private String firstName;
private String lastName;
}
Given a list of UserDto and a Pageable.Sort. I'm trying to sort the list for each defined attribute and its direction.
public Page<UserDto> listToPage(List<UserDto> list, Pageable pageable) {
var sorts = pageable.getSort();
//Retrieves the name of all object fields
var fields = Arrays.stream(UserDto.class.getDeclaredFields())
.map(Field::getName)
.collect(Collectors.toList());
//For each sort specified in the pageable I need to sort the list of objects
for (var sort : sorts) {
//If a given property does not exist. Throws an exception.
if (!fields.contains(sort.getProperty()))
throw new InvalidDataException("Property [" + sort.getProperty() + "] does not exists in " + UserDto.class.getName() + " class");
//Looks for the property and its direction
switch (sort.getProperty()) {
case "guid":
list = sort.getDirection().isDescending() ?
list.stream().sorted(Comparator.comparing(UserDto::getGuid).reversed()).collect(Collectors.toList()) :
list.stream().sorted(Comparator.comparing(UserDto::getGuid)).collect(Collectors.toList());
break;
case "firstName":
list = sort.getDirection().isDescending() ?
list.stream().sorted(Comparator.comparing(UserDto::getFirstName).reversed()).collect(Collectors.toList()) :
list.stream().sorted(Comparator.comparing(UserDto::getFirstName)).collect(Collectors.toList());
break;
case "lastName":
list = sort.getDirection().isDescending() ?
list.stream().sorted(Comparator.comparing(UserDto::getLastName).reversed()).collect(Collectors.toList()) :
list.stream().sorted(Comparator.comparing(UserDto::getLastName)).collect(Collectors.toList());
break;
default:
break;
}
}
final int start = (int) pageable.getOffset();
final int end = Math.min((start + pageable.getPageSize()), list.size());
var subList = list.subList(start, end);
return new PageImpl<>(subList, pageable, list.size());
}
All works fine, except when I pass multiple attributes to sort the users list.
The first round should sort by firstName:ASC and then, in the second round, it should be sorted by lastName:DESC keeping in mind the first sort order, but this is not doing that.
Is there any way to handle multiple sorts on the same list but in different rounds? I don't want to use thenComparing operator because I want to do this dynamically with user interaction.
A future approach is make this function in a generic way to handle any object and sort its attributes to take advantage of this JPA provided class.
This is my test case:
@Test
void testCase1() {
var userDtoList = List.of(
UserDto.builder().firstName("A3").lastName("B7").guid("G000a").build(),
UserDto.builder().firstName("A2").lastName("B4").guid("G000b").build(),
UserDto.builder().firstName("A3").lastName("B1").guid("G000c").build(),
UserDto.builder().firstName("A2").lastName("B5").guid("G000d").build(),
UserDto.builder().firstName("A1").lastName("B6").guid("G000e").build(),
UserDto.builder().firstName("A2").lastName("B3").guid("G000f").build(),
UserDto.builder().firstName("A1").lastName("B2").guid("G000g").build()
);
Sort firstNameSort = Sort.by("firstName");
Sort lastNameSort = Sort.by("lastName").descending();
Sort sort = firstNameSort.and(lastNameSort);
var pageable = PageRequest.of(0, 5, sort);
var result = userService.listToPage(userDtoList, pageable);
result.getContent().forEach(System.out::println);
assertEquals(5, result.getContent().size());
}
Solution
For someone facing the same issue I solved this creating a generic PageUtils class using Java Reflection.
You can find the code in the following Github repo:
https://github.com/rortegat/generic-pageable
Answered By - rortegat
Answer Checked By - Pedro (JavaFixing Volunteer)