skip to Main Content

I would like to filter an array given a filter object

function remove (array, filter) {
  // magic
}

filter is an object of variable size with any number of key/value

It should act as follows

const products = [
  { size: 'S', color: 'blue' },
  { size: 'L', color: 'red' },
  { size: 'S', color: 'red' },
  { size: 'L', color: 'green' },
]

// With a filter of keys/value it filters for the existing key/values
const filteredProducts1 = remove(products, { size: 'S', color: 'red' })

console.log(filteredProducts1)
/*
[
  { size: 'S', color: 'blue' },
  { size: 'L', color: 'red' },
  { size: 'L', color: 'green' },
]
*/

// With less key/value than the object array, it filters for the existing key/value
const filteredProducts2 = remove(products, { size: 'S' })

console.log(filteredProducts2)
/*
[
  { size: 'L', color: 'red' },
  { size: 'L', color: 'green' },
]
*/

// With more keys/values than the object array it discard the non-existing key/value
const filteredProducts1 = remove(products, { size: 'S', color: 'red', type: 'dress' })

console.log(filteredProducts1)
/*
[
  { size: 'S', color: 'blue' },
  { size: 'L', color: 'red' },
  { size: 'L', color: 'green' },
]
*/

My issue is in building a dynamic condition from the filter object

I’m trying to convert

{ key1: value1, key2: value2, ... , keyN: valueN }

into

condition 1 && condition2 && ... & conditionN

4

Answers


  1. You can determine whether any key-value pair in the filter is present in each object in the array:

    function remove(array, filter) {
      return array.filter((obj) => {
        for (const [key, value] of Object.entries(filter)) {
          if (obj[key] === value) return false;
        }
        return true;
      });
    }
    
    const products = [
      { size: "M", color: "blue" },
      { size: "S", color: "blue" },
      { size: "L", color: "red" },
      { size: "S", color: "red" },
      { size: "L", color: "green" },
    ];
    
    console.log(remove(products, { size: "S", color: "red" }));
    // [ { size: "M", color: "blue" }, { size: "L", color: "green" } ]
    
    console.log(remove(products, { size: "S" }));
    // [ { size: "M", color: "blue" }, { size: "L", color: "red" }, { size: "L", color: "green" } ]
    
    console.log(remove(products, { size: "S", color: "red", type: "dress" }));
    // [ { size: "M", color: "blue" }, { size: "L", color: "green" } ]
    Login or Signup to reply.
  2. you can use some to return true if at least one of the conditions is exists and not equal to filter.
    you can even use x[k] instead of x.hasOwnProperty(k) if it is guaranteed that there will be keys which point to values with falsy values (undefined,null,0..,'').
    Also MDN recommends Object.hasOwn instead of Object.hasOwnProperty if your environment (browser/Node) supports it.

    function remove (array, filter) {
      return array.filter((x) => Object.entries(filter).some(([k,v]) => x.hasOwnProperty(k) && x[k] !== v))
    }
    
    const products = [
      { size: 'S', color: 'blue' },
      { size: 'L', color: 'red' },
      { size: 'S', color: 'red' },
      { size: 'L', color: 'green' },
    ]
    
    console.log(remove(products, { size: 'S', color: 'red' }))
    console.log(remove(products, { size: 'S' }))
    console.log(remove(products, { size: 'S', color: 'red', type: 'dress' }))
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    edit
    to fix the problem

    remove(products, { type: ‘dress’ }) removes everything while it shouldn’t

    now I’m filtering out the fields from the filter entries that are existing in the object to check and returning the whole array if the filter entries is empty

    function remove (array, filter) {
      return array.filter((x,i) => {
        const entries =  Object.entries(filter).filter(([k,_]) => x.hasOwnProperty(k))
        if (entries.length === 0) return array
        return entries.some(([k,v]) => x[k] !== v)
      })
    }
    
    const products = [
      { size: 'S', color: 'blue' },
      { size: 'L', color: 'red' },
      { size: 'S', color: 'red' },
      { size: 'L', color: 'green' },
    ]
    
    console.log(remove(products, { size: 'S', color: 'red' }))
    console.log(remove(products, { size: 'S' }))
    console.log(remove(products, { size: 'S', color: 'red', type: 'dress' }))
    console.log(remove(products, { type: 'dress' }))
    console.log(remove(products, {}))
    .as-console-wrapper { max-height: 100% !important; top: 0; }
    Login or Signup to reply.
  3. How about this, variable filters will return array of boolean of matching filter [true, false, true] etc. Then just return back array item if included false in filters variable

    const products = [
        { id: 1, size: 'S', color: 'blue', type: 'dress' },
        { id: 2, size: 'L', color: 'red', type: 'dress' },
        { id: 3, size: 'S', color: 'red', type: 'dress' },
        { id: 4, size: 'L', color: 'green', type: 'shirt' },
      ]
    
    const filter1 = { size: 'S', color: 'red' }
    
    const filter2 = { size: 'S' }
    
    const filter3 = { size: 'S', color: 'red', type: 'dress' }
    
    const remove = (products, filter) => {
        return products.filter(product => {
            const filters = Object.keys(filter).map(prop => {
                return filter[prop] === product[prop]
            })
            return filters.includes(false)
        })
    }
    
    const result1 = remove(products, filter1)
    const result2 = remove(products, filter2)
    const result3 = remove(products, filter3)
    
    console.log(result1)
    console.log(result2)
    console.log(result3)
    
    Login or Signup to reply.
  4. I think you are right on track, the key to solve your problem is to proper implement a function that can decide if an item should be removed or kept based on the key-values passed, and that should be able to handle dynamically your requirements.

    Here is one suggestion for it:

    const shouldkeep = kvs => obj => {
      for (const k in kvs)
        if (k in obj && obj[k] !== kvs[k]) 
          return true
      return false
    }
    const remove = (array, filter) => array.filter(shouldkeep(filter))
    
    const products = [
      { size: 'S', color: 'blue' },
      { size: 'L', color: 'red' },
      { size: 'S', color: 'red' },
      { size: 'L', color: 'green' },
    ] 
    
    console.log(remove(products, { size: 'S', color: 'red' }))
    console.log(remove(products, { size: 'S' }))
    console.log(remove(products, { size: 'S', color: 'red', type: 'dress' }))

    A few notes about this implementation:

    • the idea of the function shouldkeep is that it iterates over the keys of your filter object, and only returns true (which means "keep") if it passes through all the keys without fully matching values
    • this syntax shouldkeep = kvs => obj => might seem weird, but it is actually very useful. It is called currying, and means you can apply one argument to the function and get back a function that expects the next argument. With this, you can do something like products, {size: 'S'}) and it returns a function that you can use as a parameter to filter. Neat!
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search