Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way of filtering by multiple fields in java

I have an entity with 10 fields:

Class Details{
  String item; 
  String name; 
  String type;
  String origin; 
  String color; 
  String quality;
  String country;
  String quantity;
  Boolean availability;
  String price;
 }

I have a restful endpoint that serves a List. I want the user to be able to provide search filters for each field. Currently I have QueryParam for each field. Then I filter by using java8 stream:

List<Detail> details;
details.stream().filter(detail-> detail.getItem()==item).filter(detail-> detail.getName()==name).....collect(Collectors.toList());

If I have 50 other classes with multiple fields that I want to filter, Is there a way of generalizing this?

like image 680
user171943 Avatar asked Mar 08 '17 18:03

user171943


People also ask

Can we have multiple filter in stream Java?

3.1.You can chain filter method on the stream to evaluate each element against multiple conditions. For example, the below code contains two filters: First filter method evaluates whether the fruit name ends with “fruit” suffix and returns the results “jack fruit” and “dragon fruit”

Can we use multiple filters in Java 8?

Overview However, we'll learn how to use the filter() method with as many condition filters as we require. More filters can be applied in a variety of methods, such using the filter() method twice or supplying another predicate to the Predicate. and() method.

Is there a filter method in Java?

Java stream offers the filter() method, which allows you to filter stream elements based on a predicate you specify. You can conveniently get only even elements from your list by using the filter method.

How do I filter a list based on another list in Java 8?

One of the utility method filter() helps to filter the stream elements that satisfy the provided criteria. The predicate is a functional interface that takes a single element as an argument and evaluates it against a specified condition.


2 Answers

You can compose such predicates with .and() and .or(), allowing you to define a single aggregate predicate that applies all the checks you would like, rather than trying to chain n .filter() calls. This enables arbitrarily complex predicates that can be constructed at runtime.

// Note that you shouldn't normally use == on objects
Predicate<Detail> itemPredicate = d-> item.equals(d.getItem());
Predicate<Detail> namePredicate = d-> name.equals(d.getName());

details.stream()
    .filter(itemPredicate.and(namePredicate))
    .collect(Collectors.toList());
like image 69
dimo414 Avatar answered Oct 03 '22 21:10

dimo414


If you want to avoid reflection how about something like this?

static enum DetailQueryParams {

    ITEM("item", d -> d.item),
    NAME("name", d -> d.name),
    TYPE("type", d -> d.type),
    ORIGIN("origin", d -> d.origin),
    COLOR("color", d -> d.color),
    QUALITY("quality", d -> d.quality),
    COUNTRY("country", d -> d.country),
    QUANTITY("quantity", d -> d.quantity),
    AVAILABILITY("availability", d -> d.availability),
    PRICE("price", d -> d.price);

    private String name;
    private Function<Detail, Object> valueExtractor;

    private DetailQueryParams(String name, 
            Function<Detail, Object> valueExtractor) {
        this.name = name;
        this.valueExtractor = valueExtractor;
    }

    public static Predicate<Detail> mustMatchDetailValues(
            Function<String, Optional<String>> queryParamGetter) {
        return Arrays.asList(values()).stream()
                .map(p -> queryParamGetter.apply(p.name)
                        .map(q -> (Predicate<Detail>) 
                                d -> String.valueOf(p.valueExtractor.apply(d)).equals(q))
                        .orElse(d -> true))
                .reduce(Predicate::and)
                .orElse(d -> true);
    }

}

And then, assuming that you can access query params by e.g. request.getQueryParam(String name) which returns a String value or null, use the code by calling the following:

details.stream()
    .filter(DetailQueryParams.mustMatchDetailValues(
            name -> Optional.ofNullable(request.getQueryParam(name))))
    .collect(Collectors.toList());

What the method basically does is:

- for each possible query param
    - get its value from the request
    - if value is present build predicate which
        - gets field value from detail object and convert to string
        - check that both strings (queried and extracted) matches
    - if value is not present return predicate that always returns true
- combine resulting predicates using and
- use always true as fallback (which here never actually happens)

Of course this could also be extended to generate predicates depending on the actual value type instead of comparing strings so that e.g. ranges could be requested and handled via "priceMin" and/or "priceMax".

like image 21
Markus Benko Avatar answered Oct 03 '22 20:10

Markus Benko