user3673623
user3673623

Reputation: 1925

javascript filter array multiple conditions

I want to simplify an array of objects. Let's assume that I have following array:

var users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
    },
    {
        name: 'Tom',
        email: '[email protected]',
        age: 35,
        address: 'England'
    },
    {
        name: 'Mark',
        email: '[email protected]',
        age: 28,
        address: 'England'
}];

And filter object:

var filter = {address: 'England', name: 'Mark'};

For example i need to filter all users by address and name, so i do loop through filter object properties and check it out:

function filterUsers (users, filter) {
    var result = [];
    for (var prop in filter) {
        if (filter.hasOwnProperty(prop)) {

            //at the first iteration prop will be address
            for (var i = 0; i < filter.length; i++) {
                if (users[i][prop] === filter[prop]) {
                    result.push(users[i]);
                }
            }
        }
    }
    return result;
}

So during first iteration when prop - address will be equal 'England' two users will be added to array result (with name Tom and Mark), but on the second iteration when prop name will be equal Mark only the last user should be added to array result, but i end up with two elements in array.

I have got a little idea as why is it happening but still stuck on it and could not find a good solution to fix it. Any help is appreciable. Thanks.

Upvotes: 161

Views: 646760

Answers (29)

Giovanni Gadaleta
Giovanni Gadaleta

Reputation: 1

Vue3 Pinia

I put the answer of trincot in Vue 3 Pinia context:

state: () => ({

    data: [{...}, {...}, ...],

    filter: {address: ['England', 'Scotland'], name: ['Mark'] }

}),

getters: {

    list: (state) => {

        return state.data.filter(obj =>
            Object.entries(state.filter).every(([prop, find]) => find.includes(obj[prop]))
        );
    },
}

Upvotes: 0

fruitloaf
fruitloaf

Reputation: 2892

Here is the basic function for this:

const getValue = (value) =>
    typeof value === "string" ? value.toUpperCase() : value;


function filterPlainArray(array, filters) {
    const filterKeys = Object.keys(filters);
    return array.filter((item) => {
        return filterKeys.every((key) => {
            if (!filters[key].length) {
                return true;
            }
            return filters[key].find(
                (filter) => getValue(filter) === getValue(item[key])
            );
        });
    });
}

let filters = {
    color: ["yellow"],  OR  ["yellow", "orange"] // if you want many options
    rentedOut: [true],
};


let a = filterPlainArray(data, filters);
console.log(a);
console.log(a.length);

if you need RANGE e.g. prices, then you need to add this function:

function range(start, end) {
    start = parseFloat(start);
    end = parseFloat(end);
    return new Float32Array(end - start).fill().map((d, i) => i + start);
}

now your filters should look like this:

let filters = {
    color: [],
    rentedOut: [],
    miles: range(50000, 125000),
};

Sources:

Upvotes: 0

KARTHIKEYAN.A
KARTHIKEYAN.A

Reputation: 20088

We can use different operators to provide multiple condtion to filter the array in the following way

Useing OR (||) Operator:

const orFilter = [{a:1, b: 3}, {a:1,b:2}, {a: 2, b:2}].filter(d => (d.a !== 1 || d.b !== 2))
console.log(orFilter, 'orFilter')

Using AND (&&) Operator:

const andFilter = [{a:1, b: 3}, {a:1,b:2}, {a: 2, b:2}].filter(d => (d.a !== 1 && d.b !== 2))
console.log(andFilter, 'andFilter')

Upvotes: 4

wahyu setiawan
wahyu setiawan

Reputation: 207

users.filter(o => o.address == 'England' && o.name == 'Mark')

Much better for es6. or you can use || (or) operator like this

users.filter(o => {return (o.address == 'England' || o.name == 'Mark')})

Upvotes: 20

Scott Sauyet
Scott Sauyet

Reputation: 50787

A question I was in the middle of answering got (properly) closed as duplicate of this. But I don't see any of the answers above quite like this one. So here's one more option.

We can write a simple function that takes a specification such as {name: 'mike', house: 'blue'}, and returns a function that will test if the value passed to it matches all the properties. It could be used like this:

const where = (spec, entries = Object .entries (spec)) => (x) =>
  entries .every (([k, v]) => x [k] == v)

const users = [{name: 'John', email: '[email protected]', age: 25, address: 'USA'}, {name: 'Mark', email: '[email protected]', age: 25, address: 'USA'}, {name: 'Tom', email: '[email protected]', age: 35, address: 'England'}, {name: 'Mark', email: '[email protected]', age: 28, address: 'England'}]

console .log ('Mark', users .filter (where ({name: 'Mark'})))
console .log ('England', users .filter (where ({address: 'England'})))
console .log ('Mark/England', users .filter (where ({name: 'Mark', address: 'England'})))
.as-console-wrapper {max-height: 100% !important; top: 0}

And if we wanted to wrap the filtering into a single function, we could reuse that same function, wrapped up like this:

const where = (spec, entries = Object .entries (spec)) => (x) =>
  entries .every (([k, v]) => x [k] == v)

const filterBy = (spec) => (xs) => 
  xs .filter (where (spec))

const users = [{name: 'John', email: '[email protected]', age: 25, address: 'USA'}, {name: 'Mark', email: '[email protected]', age: 25, address: 'USA'}, {name: 'Tom', email: '[email protected]', age: 35, address: 'England'}, {name: 'Mark', email: '[email protected]', age: 28, address: 'England'}]


console .log ('Mark/England', filterBy ({address: "England", name: "Mark"}) (users))
.as-console-wrapper {max-height: 100% !important; top: 0}

(Of course that last doesn't have to be curried. We could change that so that we could call it with two parameters at once. I find this more flexible, but YMMV.)

Keeping it as a separate function has the advantage that we could then reuse it, in say, a find or some other matching situation.


This design is very similar to the use of where in Ramda (disclaimer: I'm one of Ramda's authors.) Ramda offers the additional flexibility of allowing arbitrary predicates instead of values that have to be equal. So in Ramda, you might write something like this instead:

filter (where ({
  address: equals ('England')
  age: greaterThan (25)
}) (users)

It's much the same idea, only a bit more flexible.

Upvotes: 1

Nikhilus
Nikhilus

Reputation: 9

Please check below code snippet with data you provided, it will return filtered data on the basis of multiple columns.

var filter = {
  address: 'India',
  age: '27'
};

var users = [{
    name: 'Nikhil',
    email: '[email protected]',
    age: 27,
    address: 'India'
  },
  {
    name: 'Minal',
    email: '[email protected]',
    age: 27,
    address: 'India'
  },
  {
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];


function filterByMultipleColumns(users, columnDataToFilter) {
  return users.filter(row => {
    return Object.keys(columnDataToFilter).every(propertyName => row[propertyName].toString().toLowerCase().indexOf(columnDataToFilter[propertyName].toString().toLowerCase()) > -1);
  })
}

var filteredData = filterByMultipleColumns(users, filter);

console.log(filteredData);

Result : [ { "name": "Nikhil", "email": "[email protected]", "age": 27, "address": "India" }, { "name": "Minal", "email": "[email protected]", "age": 27, "address": "India" } ]

Please check below link which can used with just small changes Javascript filter array multiple values – example

Upvotes: 0

pmsoltani
pmsoltani

Reputation: 1222

Improving on the good answers here, below is my solution:

const rawData = [
  { name: 'John', email: '[email protected]', age: 25, address: 'USA' },
  { name: 'Tom', email: '[email protected]', age: 35, address: 'England' },
  { name: 'Mark', email: '[email protected]', age: 28, address: 'England' }
]
const filters = { address: 'England', age: 28 }

const filteredData = rawData.filter(i =>
  Object.entries(filters).every(([k, v]) => i[k] === v)
)

Upvotes: 11

Jose Velasco
Jose Velasco

Reputation: 175

My solution, based on NIKHIL C M solution:

 let data = [
    { 
      key1: "valueA1", 
      key2: "valueA2",
      key3: []
    },{
      key1: "valueB1", 
      key2: "valueB2"
      key3: ["valuesB3"]
    }
 ];

 let filters = {
    key1: "valueB1",
    key2: "valueB2"
 };

 let filteredData = data.filter((item) => {
     return Object.entries(filters).every(([filter, value]) => {
          return item[filter] === value;
          //Here i am applying a bit more logic like 
          //return item[filter].includes(value) 
          //or filter with not exactly same key name like
          //return !isEmpty(item.key3)
     });
 });

Upvotes: 1

Mohak Londhe
Mohak Londhe

Reputation: 392

arr.filter((item) => {
       if(condition)
       {
         return false;
       }
       return true;
    });

Upvotes: -1

NIKHIL C M
NIKHIL C M

Reputation: 4236

Dynamic filters with AND condition

Filter out people with gender = 'm'

var people = [
    {
        name: 'john',
        age: 10,
        gender: 'm'
    },
    {
        name: 'joseph',
        age: 12,
        gender: 'm'
    },
    {
        name: 'annie',
        age: 8,
        gender: 'f'
    }
]
var filters = {
    gender: 'm'
}

var out = people.filter(person => {
    return Object.keys(filters).every(filter => {
        return filters[filter] === person[filter]
    });
})


console.log(out)

Filter out people with gender = 'm' and name = 'joseph'

var people = [
    {
        name: 'john',
        age: 10,
        gender: 'm'
    },
    {
        name: 'joseph',
        age: 12,
        gender: 'm'
    },
    {
        name: 'annie',
        age: 8,
        gender: 'f'
    }
]
var filters = {
    gender: 'm',
    name: 'joseph'
}

var out = people.filter(person => {
    return Object.keys(filters).every(filter => {
        return filters[filter] === person[filter]
    });
})


console.log(out)

You can give as many filters as you want.

Upvotes: 8

LuisDev99
LuisDev99

Reputation: 1837

Using lodash and not pure javascript

This is actually quite simple using lodash and very easy to add/modify filters.

import _ from 'lodash';

async getUsersWithFilter(filters) {
     const users = yourArrayOfSomethingReally();

    // Some properties of the 'filters' object can be null or undefined, so create a new object without those undefined properties and filter by those who are defined
    const filtersWithoutUndefinedValuesObject = _.omitBy(
      filters,
      _.isNil,
    );

    return _.filter(users, { ...filtersWithoutUndefinedValuesObject });
}
  1. The omitBy function checks your filters object and removes any value that is null or undefined (if you take it out, the lodash.filter function wont return any result.

  2. The filter function will filter out all the objects who's values don't match with the object you pass as a second argument to the function (which in this case, is your filters object.)

Why use this?

Well, assume you have this object:

const myFiltersObj = {

   name: "Java",
   age: 50

};

If you want to add another filter, just add a new property to the myFilterObj, like this:

const myFiltersObj = {

   name: "Java",
   email: 50,
   country: "HND"

};

Call the getUsersWithFilter function, and it will work just fine. If you skip, let's say the name property in the object, the getUsersWithFilter function will filter by the email and country just fine.

Upvotes: 0

Kaloyan Stoyanov
Kaloyan Stoyanov

Reputation: 129

A clean and functional solution

const combineFilters = (...filters) => (item) => {
    return filters.map((filter) => filter(item)).every((x) => x === true);
};

then you use it like so:

const filteredArray = arr.filter(combineFilters(filterFunc1, filterFunc2));

and filterFunc1 for example might look like this:

const filterFunc1 = (item) => {
  return item === 1 ? true : false;
};

Upvotes: 4

Coşkun Deniz
Coşkun Deniz

Reputation: 2279

I think this might help.

const filters = ['a', 'b'];

const results = [
  {
    name: 'Result 1',
    category: ['a']
  },
  {
    name: 'Result 2',
    category: ['a', 'b']
  },
  {
    name: 'Result 3',
    category: ['c', 'a', 'b', 'd']
  }
];

const filteredResults = results.filter(item =>
  filters.every(val => item.category.indexOf(val) > -1)
);

console.log(filteredResults);
  

Upvotes: 7

Arvind Chourasiya
Arvind Chourasiya

Reputation: 17422

If you want to put multiple conditions in filter, you can use && and || operator.

var product= Object.values(arr_products).filter(x => x.Status==status && x.email==user)

Upvotes: 2

Abhishek Chandran
Abhishek Chandran

Reputation: 1646

If you know the name of the filters, you can do it in a line.

users = users.filter(obj => obj.name == filter.name && obj.address == filter.address)

Upvotes: 103

trincot
trincot

Reputation: 350262

You'll have more flexibility if you turn the values in your filter object into arrays:

var filter = {address: ['England'], name: ['Mark'] };

That way you can filter for things like "England" or "Scotland", meaning that results may include records for England, and for Scotland:

var filter = {address: ['England', 'Scotland'], name: ['Mark'] };

With that setup, your filtering function can be:

const applyFilter = (data, filter) => data.filter(obj =>
    Object.entries(filter).every(([prop, find]) => find.includes(obj[prop]))
);

// demo
var users = [{name: 'John',email: '[email protected]',age: 25,address: 'USA'},{name: 'Tom',email: '[email protected]',age: 35,address: 'England'},{name: 'Mark',email: '[email protected]',age: 28,address: 'England'}];var filter = {address: ['England'], name: ['Mark'] };
var filter = {address: ['England'], name: ['Mark'] };

console.log(applyFilter(users, filter));

Upvotes: 3

Jev Belovs
Jev Belovs

Reputation: 1

const users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];

const filteredUsers = users.filter(({ name, age }) => name === 'Tom' && age === 35)

console.log(filteredUsers)

Upvotes: 0

Tangu Achilis
Tangu Achilis

Reputation: 59

This is another method i figured out, where filteredUsers is a function that returns the sorted list of users.

var filtersample = {address: 'England', name: 'Mark'};

filteredUsers() {
  return this.users.filter((element) => {
    return element['address'].toLowerCase().match(this.filtersample['address'].toLowerCase()) || element['name'].toLowerCase().match(this.filtersample['name'].toLowerCase());
  })
}

Upvotes: 0

Stax
Stax

Reputation: 1

This is an easily understandable functional solution

let filtersObject = {
  address: "England",
  name: "Mark"
};

let users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];

function filterUsers(users, filtersObject) {
  //Loop through all key-value pairs in filtersObject
  Object.keys(filtersObject).forEach(function(key) {
    //Loop through users array checking each userObject
    users = users.filter(function(userObject) {
      //If userObject's key:value is same as filtersObject's key:value, they stay in users array
      return userObject[key] === filtersObject[key]
    })
  });
  return users;
}

//ES6
function filterUsersES(users, filtersObject) {
  for (let key in filtersObject) {
    users = users.filter((userObject) => userObject[key] === filtersObject[key]);
  }
  return users;
}

console.log(filterUsers(users, filtersObject));
console.log(filterUsersES(users, filtersObject));

Upvotes: 0

Richard Richie
Richard Richie

Reputation: 61

In lodash,

_.filter(users,{address: 'England', name: 'Mark'})

In es6,

users.filter(o => o.address == 'England' && o.name == 'Mark')

Upvotes: 5

Hamza Khanzada
Hamza Khanzada

Reputation: 1529

Using Array.Filter() with Arrow Functions we can achieve this using

users = users.filter(x => x.name == 'Mark' && x.address == 'England');

Here is the complete snippet

// initializing list of users
var users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];

//filtering the users array and saving 
//result back in users variable
users = users.filter(x => x.name == 'Mark' && x.address == 'England');


//logging out the result in console
console.log(users);

Upvotes: 11

Hamo
Hamo

Reputation: 1

const data = [{
    realName: 'Sean Bean',
    characterName: 'Eddard “Ned” Stark'
}, {
    realName: 'Kit Harington',
    characterName: 'Jon Snow'
}, {
    realName: 'Peter Dinklage',
    characterName: 'Tyrion Lannister'
}, {
    realName: 'Lena Headey',
    characterName: 'Cersei Lannister'
}, {
    realName: 'Michelle Fairley',
    characterName: 'Catelyn Stark'
}, {
    realName: 'Nikolaj Coster-Waldau',
    characterName: 'Jaime Lannister'
}, {
    realName: 'Maisie Williams',
    characterName: 'Arya Stark'
}];

const filterKeys = ['realName', 'characterName'];


const multiFilter = (data = [], filterKeys = [], value = '') => data.filter((item) => filterKeys.some(key => item[key].toString().toLowerCase().includes(value.toLowerCase()) && item[key]));


let filteredData = multiFilter(data, filterKeys, 'stark');

console.info(filteredData);
/* [{
  "realName": "Sean Bean",
  "characterName": "Eddard “Ned” Stark"
}, {
  "realName": "Michelle Fairley",
  "characterName": "Catelyn Stark"
}, {
  "realName": "Maisie Williams",
  "characterName": "Arya Stark"
}]
 */

Upvotes: -1

nils petersohn
nils petersohn

Reputation: 2407

with the composition of some little helpers:

const filter = {address: 'England', name: 'Mark'};
console.log( 
  users.filter(and(map(propMatches)(filter)))
)

function propMatches<T>(property: string, value: any) {
  return (item: T): boolean => item[property] === value
}

function map<T>(mapper: (key: string, value: any, obj: T) => (item:T) => any) {
  return (obj: T) => {
    return Object.keys(obj).map((key) => {
      return mapper(key, obj[key], obj)
    });
  }
}

export function and<T>(predicates: ((item: T) => boolean)[]) {
  return (item: T) =>
    predicates.reduce(
        (acc: boolean, predicate: (item: T) => boolean) => {
            if (acc === undefined) {
                return !!predicate(item);
            }
            return !!predicate(item) && acc;
        },
        undefined // initial accumulator value
    );
}

Upvotes: 0

SoEzPz
SoEzPz

Reputation: 15912

Another take for those of you that enjoy succinct code.

NOTE: The FILTER method can take an additional this argument, then using an E6 arrow function we can reuse the correct this to get a nice one-liner.

var users = [{name: 'John',email: '[email protected]',age: 25,address: 'USA'},
             {name: 'Tom',email: '[email protected]',age: 35,address: 'England'},
             {name: 'Mark',email: '[email protected]',age: 28,address: 'England'}];

var query = {address: "England", name: "Mark"};

var result = users.filter(search, query);

function search(user){
  return Object.keys(this).every((key) => user[key] === this[key]);
}




// |----------------------- Code for displaying results -----------------|
var element = document.getElementById('result');

function createMarkUp(data){
  Object.keys(query).forEach(function(key){
    var p = document.createElement('p');
    p.appendChild(document.createTextNode(
    key.toUpperCase() + ': ' + result[0][key]));
    element.appendChild(p);
  });
}

createMarkUp(result);
<div id="result"></div>

Upvotes: 33

Houssem Zitoun
Houssem Zitoun

Reputation: 11

functional solution

function applyFilters(data, filters) {
  return data.filter(item =>
    Object.keys(filters)
      .map(keyToFilterOn =>
        item[keyToFilterOn].includes(filters[keyToFilterOn]),
      )
      .reduce((x, y) => x && y, true),
  );
}

this should do the job

applyFilters(users, filter);

Upvotes: 1

Hemadri Dasari
Hemadri Dasari

Reputation: 33994

Here is ES6 version of using arrow function in filter. Posting this as an answer because most of us are using ES6 these days and may help readers to do filter in advanced way using arrow function, let and const.

const filter = {
  address: 'England',
  name: 'Mark'
};
let users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];


users= users.filter(item => {
  for (let key in filter) {
    if (item[key] === undefined || item[key] != filter[key])
      return false;
  }
  return true;
});

console.log(users)

Upvotes: 17

Raghavendra
Raghavendra

Reputation: 3580

You can do like this

var filter = {
  address: 'England',
  name: 'Mark'
};
var users = [{
    name: 'John',
    email: '[email protected]',
    age: 25,
    address: 'USA'
  },
  {
    name: 'Tom',
    email: '[email protected]',
    age: 35,
    address: 'England'
  },
  {
    name: 'Mark',
    email: '[email protected]',
    age: 28,
    address: 'England'
  }
];


users= users.filter(function(item) {
  for (var key in filter) {
    if (item[key] === undefined || item[key] != filter[key])
      return false;
  }
  return true;
});

console.log(users)

Upvotes: 179

Diogo Rodrigues
Diogo Rodrigues

Reputation: 1332

Can also be done this way:

    this.users = this.users.filter((item) => {
                return (item.name.toString().toLowerCase().indexOf(val.toLowerCase()) > -1 ||
                item.address.toLowerCase().indexOf(val.toLowerCase()) > -1 ||
                item.age.toLowerCase().indexOf(val.toLowerCase()) > -1 ||
                item.email.toLowerCase().indexOf(val.toLowerCase()) > -1);
            })

Upvotes: 10

The_Black_Smurf
The_Black_Smurf

Reputation: 5269

If the finality of you code is to get the filtered user, I would invert the for to evaluate the user instead of reducing the result array during each iteration.

Here an (untested) example:

function filterUsers (users, filter) {
    var result = [];

    for (i=0;i<users.length;i++){
        for (var prop in filter) {
            if (users.hasOwnProperty(prop) && users[i][prop] === filter[prop]) {
                result.push(users[i]);
            }
        }
    }
    return result;
}

Upvotes: 0

Related Questions