Home > Net >  Map<String, List<? extends Object>> filtering issue with Stream API
Map<String, List<? extends Object>> filtering issue with Stream API

Time:09-11

I have Employee object that has 3 fields: name, role, companyName.

I need to filter Employee objects based on a role property.

Despite that I'm applying filter in the stream in the code below, it's returning the entire data.

My code:

public static Map<String, List<? extends Employee>> testMappingFilter(List<Employee> emp) {
    Map<String, List<? extends Employee>> dataInput = new HashMap<>();
    for (Employee entry : emp) {
        dataInput.put("senior", new ArrayList(emp));
        dataInput.put("junior", new ArrayList(emp));
    }
    
    Map<String, List<? extends Employee>> dataResponse = new HashMap<>();
    
    Set<String> realmroles = new HashSet<>();
    realmroles.add("admin");
    realmroles.add("user");
    
    Either<Map<String, List<? extends Employee>>, ResponseFormat> followedResourcesServices = Either.left(dataInput);
    
    realmroles.stream().forEach(role -> 
        followedResourcesServices.left().value().entrySet().stream()
                .filter(elements -> elements.getValue().stream().anyMatch(i -> ((Employee) i).getRole().contains(role)))
            .forEach(elements -> dataResponse.put(elements.getKey(), elements.getValue())));
    
    return dataResponse;
}

Sample data:

Expected Output:

(expected roles are only "admin" and "user", data related to role "super" should not come)

{ “senior” :
 [ { ”name”:”ABC”, “role”:”admin”, “companyName”:”ABC &Co” },
 { ”name”:”XYZ”, “role”:”admin”, “companyName”:”XYZ &Co” },
 { ”name”:”RST”, “role”:”user”, “companyName”:”RST &Co” } ], “junior” :
 [ { ”name”:”ABC”, “role”:”admin”, “companyName”:”ABC &Co” },
 { ”name”:”XYZ”, “role”:”admin”, “companyName”:”XYZ &Co” },
 { ”name”:”RST”, “role”:”user”, “companyName”:”RST &Co” } ] 
}

Actual output:

{ “senior” :
 [ { ”name”:”ABC”, “role”:”admin”, “companyName”:”ABC &Co” },
 { ”name”:”DDD”, “role”:”super”, “companyName”:”DDD &Co” },
 { ”name”:”XYZ”, “role”:”admin”, “companyName”:”XYZ &Co” },
 { ”name”:”RST”, “role”:”user”, “companyName”:”RST &Co” } ], “junior” :
 [ { ”name”:”ABC”, “role”:”admin”, “companyName”:”ABC &Co” },
 { ”name”:”DDD”, “role”:”super”, “companyName”:”DDD &Co” },
 { ”name”:”XYZ”, “role”:”admin”, “companyName”:”XYZ &Co” },
 { ”name”:”RST”, “role”:”user”, “companyName”:”RST &Co” } ] 
}

CodePudding user response:

There are several issues in your code:

  • The filtering logic is incorrect: you said you need to retain only roles "admin" and "user", but according to your code any List of Employee that contains at least one employee with either of these roles is good to go (meaning the whole list, not only employee having the required role). That's why you see roles ”super” in your output. You need to filter the employees in each list, instead of filtering the lists.

  • That's not a job for Stream.forEach, which operates via side-effects. It's discouraged to be used for in place of reduction operations. Have a look at the API documentation, especially pay attention to the code examples.

  • Instead of iterating over the Set of roles, we can simply use contains() check, and iterate only over the entries of the map which you're retrieving by calling Either.left(dataInput).value().

To accumulate the values from the stream we have a wide range of operations, and the most suitable of them in this case is collect(), because we need to perform mutable reduction.

We can't apply the filtering logic inside the stream without creating new objects (which costs performance). Another option we have is to perform filtering inside a Collector while accumulating the data.

I'll with the second option. In order to generate the resulting map, we can use collector groupingBy() in conjunction with flatMapping() toList() applied as the downstream.

Map<String, List<? extends Employee>> dataInput = // initializing dataInput
        
Set<String> realmroles = // initializing realmroles

return Either.left().value().entrySet().stream()
    .collect(Collectors.groupingBy(
        Map.Entry::getKey,
        Collectors.flatMapping(entry -> entry.getValue().stream()
                .filter(emp -> realmroles.contains(((Employee) emp).getRole())),
            Collectors.toList())
    ));

CodePudding user response:

You have to set the value (emp list) in your map.

followedResourcesServices.left().value().entrySet().stream().forEach(e -> {
        e.setValue(e.getValue().stream().filter(i -> realmroles.stream().anyMatch(role -> ((Employee) i).getRole().equals(role))).
                collect(Collectors.toList()));
        dataResponse.put(e.getKey(), e.getValue());
    });
  • Related