skip to Main Content

I’m trying to filter an array of objects based on the objects found into another array.
Basically, I need to filter a list based on some filters selected by the user.

Assuming my list of results is something like this:

[
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5}
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5}
 {uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5}
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5}
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5}
....
]

and the list I need to use as a filter list is a dynamic list ( I don’t know which values or keys will be present in this list since it is selected by the user on the ui)
that might look like this:

[
 {key: 'type', value: '2'},
 {key: 'style', value: '4'}
]

what I am trying to achieve is a filtered list that returns only the values that have both the key-value pair above true

I have checked many answers here on stack overflow, just to mention a few:

How to check if a value exists in an object using JavaScript

How to efficiently check if a Key Value pair exists in a Javascript "dictionary" object

lodash: filter array of objects with a different array of objects

Filtering for Multiple Fields in Object Array in Javascript/Lodash – Stack Overflow

and many more, but with no luck.

What I currently have is something that looks like this:

...
//Here I am transforming the filters into the array with format {key: '', value: ''} 
// that I was mentioning before. This is not mandatory, If you recon it is not needed
// I can easily remove this. The default format for my filters before converting them
// to array looks like this {type: 2, style: 4}

const filterArr = Object.keys(cleanedFilters).map(key => ({ key, value: cleanedFilters[key] }))



const result = flatten(
  map(filterArr, function (fil) {
    return filter(searchResults, function (a) {
          return a.hasOwnProperty(fil.key) && a[fil.key] === parseInt(fil.value)        
    })
  })
)

the result of this snipper is an array that looks like this:

[
    {
        "uuid": 1,
        "type": 2,
        "style": 3,
        "somethingelse": "4",
        "anotherval": 5
    },
    {
        "uuid": 2,
        "type": 4,
        "style": 4,
        "somethingelse": "4",
        "anotherval": 5
    },
    {
        "uuid": 3,
        "type": 6,
        "style": 4,
        "somethingelse": "4",
        "anotherval": 5
    }
]

which is basically appending everything that has both style = 4 and type = 2

My desired result is a list with only the objects that match all the filters (which, in this case, should have been an empty array since I don’t have an entry with style 4 and type 2

As you can see from the example snipper I don’t mind using lodash if needed.

Thanks in advance for any possible suggestions you might have to provide

3

Answers


  1. Just filter the array with all items in the filter matching.
    The only problem that your filter contains string values but the array does number ones. So there’re several ways to compare, just choose the most appropriate for you.

    const arr = [
     {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
     {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
     {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
    ];
    
    const filter = [
     {key: 'type', value: '2'},
     {key: 'style', value: '4'}
    ];
    
    const filtered = arr.filter(item => filter.every(({key, value}) => item[key] === +value));
     
    console.log(filtered);

    As you mentioned your filter is initially an object, so this could be done like this also:

    const arr = [
     {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
     {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
     {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
    ];
    
    const filter = {type: '2', style:'4'};
    
    const filtered = arr.filter(item => Object.entries(filter).every(([key, value]) => item[key] === +value));
     
    console.log(filtered);

    If you want the best performance (for example in case of filtering a big array from the backend) you could generate a filter function:

    const arr = [
     {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
     {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
     {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
    ];
    
    const filter = {type: '2', style:'4'};
    
    const filterFn = new Function('item', 'return ' + Object.entries(filter).map(([key, val]) => `item.${key} === ${val}`).join(' && '));     
    
    console.log('filterFn:', filterFn);
    
    const filtered = arr.filter(filterFn);    
    
    console.log(filtered);

    And the benchmark:

    enter image description here

    <script benchmark data-count="10">
    
    const arr = [];
    
    const chunk = [
     {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
     {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
     {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
    ];
    
    let count = 1000000;
    while(count--){
      arr.push(...chunk);
    }
    
    const filter = {type: '2', style:'4'};
    const filterFn = new Function('item', 'return ' + Object.entries(filter).map(([key, val]) => `item.${key} === ${val}`).join(' && '));
    
    
    // @benchmark Array.every
    
    const filterArr = Object.entries(filter).map(([key, value]) => ({key, value: +value}));
    arr.filter(item => filterArr.every(({key, value}) => item[key] === value));
    
    // @benchmark Function generation
    arr.filter(filterFn);
    
     
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  2. As you iterate over the object in the data array use every to check that every object key/value in the query passes the condition that it exists in the current iterated object.

    const data = [
     {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
     {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5},
     {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
     {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5}
    ];
    
    const query = [
     {key: 'type', value: '2'},
     {key: 'style', value: '4'}
    ];
    
    const query2 = [
     {key: 'type', value: '2'},
     {key: 'style', value: '3'}
    ];
    
    function filterData(data, query) {
      return data.filter(obj => {
        
        // Return true if every key/value pair in the
        // current iterated query object exists in the
        // current iterated data object
        // otherwise return false
        return query.every(q => {
          return obj[q.key] === +q.value;
        });
      });
    }
    
    console.log(filterData(data, query));
    console.log(filterData(data, query2));
    Login or Signup to reply.
  3. If you expect valid integers as the values, you’ll need to validate that user input (in the parseFilter function below). Beyond that, to filter according to the intersection of every filter value (boolean AND), you can use Array.prototype.every():

    TS Playground

    function parseFilter(
      input: Readonly<Record<"key" | "value", string>>,
    ): { key: string; value: number; } {
      const value = Number(input.value);
      // Validate according to your criteria:
      if (
        !(
          input.value // Not empty string, which would have been coerced to 0
          && Number.isInteger(value) // Is integer
        )
      ) throw new Error(`Invalid filter value: ${input.value}`);
      return { key: input.key, value };
    }
    
    const input: Record<string, number>[] = [
      { uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5 },
      { uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5 },
      { uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5 },
      { uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5 },
      { uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5 },
    ];
    
    const filters = [
      { key: "type", value: "2" },
      { key: "style", value: "4" },
    ];
    
    const parsed = filters.map(parseFilter);
    
    const filtered = input.filter((o) =>
      parsed.every(({ key, value }) => o[key] === value)
    );
    
    console.log(filtered);
    
    

    Compiled JS:

    "use strict";
    function parseFilter(input) {
        const value = Number(input.value);
        if (!(input.value
            && Number.isInteger(value)))
            throw new Error(`Invalid filter value: ${input.value}`);
        return { key: input.key, value };
    }
    const input = [
        { uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5 },
        { uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5 },
        { uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5 },
        { uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5 },
        { uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5 },
    ];
    const filters = [
        { key: "type", value: "2" },
        { key: "style", value: "4" },
    ];
    const parsed = filters.map(parseFilter);
    const filtered = input.filter((o) => parsed.every(({ key, value }) => o[key] === value));
    console.log(filtered);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search