Filter array by object property values - javascript

I am programming a function that will handle javascript array filtering. I know the values by which I want to filter so I know how to do it in a fairly easy way, but I would like the code to be more extensible.
I wrote such a function:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter((item) => {
if (filters.prop3 && filters.prop2) {
return item.prop3 === filters.prop3 && item.prop2 === filters.prop2;
}
if (filters.prop3) {
return item.prop3 === filters.prop3;
}
if (filters.prop2) {
return item.prop2 === filters.prop2;
}
});
}
I am not completely satisfied with it. I think it could be written better. If the 3rd argument comes, I don't want to add it to the if - it should be automatic.
I've searched several topics on stackoverflow, looked through the lodash documentation looking for some good solution but I have no idea what I can do better with this.

If I understood correctly, you want to only keep the items that match all the filter:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = {prop2: 'Paris', prop3: '987'};
const handleFilters = (items, filters) => {
return items.filter((item) =>
Object.entries(filters).every(([key, val]) => item[key] === val)
)
};
console.log(handleFilters(items, filters))
This basically checks that every (key, val) of the filter exists in the item

private handleFilters (items, ...props) {
return items.filter(item => props.every(prop => item[prop] === prop));
}

To use the filter object dynamically, you could look into using something like Object.keys().
Here's your example code changed to use that:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter(item => {
return Object.keys(filters).some(filterKey => item[filterKey] === filters[filterKey])
});
}
It loops through the items, the same as you already were doing. However, now it also loops through the keys set in the filters object. The some() function returns a boolean; true if the callback is true and false if the callback is false. Here it checks if the value in item[filterKey] is the same as the value in filters[filterKey].
It's important to note that this returns the item if any of the filter values matches with an item's property. If all values must be in the filtered item, you'll want to use Object.keys(filters).every(filterKey => item[filterKey] === filters[filterKey]).
The every function returns true only if all filterKeys have the same value as the keys checked in item.
It is also possible to use Object.entries(), which allows directly using the value, but for this example I chose to use just the keys, for consistency.

You can filter by using Object.key , filter and every method in precise manner.
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilter = (items, filters) => {
return items.filter((item) => Object.keys(filters).every((key) => item[key] === filters[key]));
}
console.log(handleFilter(items, filters))

You can remove your multiple if blocks by using for..in
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter((item) => {
for(const key in filters) {
if (filters[key] !== item[key]) {
return false;
}
}
return true;
});
}
console.log(handleFilters(items, filters))
It does the same as your code was doing.

Some Improvement over #David Alvarez approach, here i have used Object.keys instead of Object.entries as Object.key is faster than Object.entries here is the comparison: https://www.measurethat.net/Benchmarks/Show/3685/0/objectentries-vs-objectkeys-vs-objectkeys-with-extra-ar
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = {prop2: 'Paris', prop3: '987'};
const handleFilters = (items, filters) => (
items.filter((item) =>
Object.keys(filters).every(key => item[key] === filters[key])
)
);
console.log(handleFilters(items, filters))

Related

Create an array based on two objects

I have two objects, that contains same number of properties:
object1: {
prop1: 'data12',
prop2: 'data13',
prop3: 'data58',
prop4: 'xyz',
// more props here...
}
object2: {
prop1: 123,
prop2: 'email#gmail.com',
prop3: 'text',
prop4: 'bla',
// more props here...
}
I want to build an array {name: '', value: ''} based on this objects:
[
{ data12: 123 },
{ data13: 'email#gmail.com' },
{ data58: text },
{ xyz: 'bla' },
]
How can you do that? The number of properties could be different.
Just using Object.keys() can do it
let object1 = {
prop1: 'data12',
prop2: 'data13',
prop3: 'data58',
prop4: 'xyz',
}
let object2 = {
prop1: '123',
prop2: 'email#gmail.com',
prop3: 'text',
prop4: 'bla',
}
let result =[]
let obj = {}
Object.keys(object1).forEach(k =>{
obj[object1[k]] = object2[k]
})
result.push(obj)
// combined with reduce can also do it
/*
let obj =Object.keys(object1).reduce((a,c) => {
a[object1[c]] = object2[c]
return a
},{})
*/
result.push(obj)
console.log(result)

Create array from the values that are looped in for...in

I was getting this weird result when I'm trying to construct an array of objects by using for...in loop for a object
const obj = {
name: 'John',
age: '25'
}
for (const property in obj) {
const obj1 = {
prop1: property,
prop2: obj[property]
}
const result = [].push(obj)
console.log(result)
}
I was expecting the result to be
[{prop1: 'name', prop2: 'John'}, {prop1: 'age', prop2: '25'}]
Could anyone please help?
push returns the new length of the array, which is why you see 1. Move the array initialization out of the loop and log after the loop is finished:
const obj = {
name: 'John',
age: '25'
}
const result = []
for (const property in obj) {
const obj1 = {
prop1: property,
prop2: obj[property]
}
result.push(obj1)
}
console.log(result)
You are pushing to a new Array that is scoped inside of your loop. You really want to do:
const obj = {
name: 'John',
age: '25'
}
const results = [];
for(let p in obj){
results.push({
prop1: p,
prop2: obj[p]
});
}
console.log(results)

Remove objects from a deeply nested object array by property value

Consider I have a nested object array. One possible example scenario could be:
content: [
{
prop1: someValue,
prop2: someValue,
content: [
{
prop2: someValue,
prop3: someValue,
myProperty: myValue
},
{
prop1: someValue,
prop3: someValue,
myProperty: otherValue
}
]
},
{
prop5: someValue,
prop2: someValue
}
]
Here are the possibilities:
The structure starts with content[] but the descendants may or may not have content property.
The level of the hierarchy can be of any number.
The properties contained by the objects are not always the same i.e. one object may have x, y, z properties while the other may have v, w, z properties.
If any object in the hierarchy has myProperty key, there won't be content key.
More than one object in the hierarchy can have myProperty with value'myValue.
My requirement:
If at any level an object has the property myProperty with the value myValue then remove the entire object (NOT JUST THE PROPERTY) from the hierarchy.
My attempt so far:
private removeObjects(content: any, values: string[]): any {
if (!content || content.length === 0) {
return
}
content = content.filter((c) => {
if (!c.myProperty) return true
return c.myProperty.indexOf(values) > 0
})
// Here is my problem since I am supposed to do a recursive call on each of child contents,
// how do I merge back the original array?
return this.removeObjects(content, values)
}
The following recursively returns a new array without mutating the original
const content = [{
prop1: "someValue",
prop2: "someValue",
content: [{
prop2: "someValue",
prop3: "someValue",
myProperty: "myValue"
},
{
prop1: "someValue",
prop3: "someValue",
myProperty: "otherValue"
}
]
},
{
prop5: "someValue",
prop2: "someValue"
}
]
function removeObjects(content) {
return content.reduce((arr, obj) => {
if (obj["myProperty"] && obj["myProperty"] === "myValue") {
return arr
} else if (obj["content"] && obj["content"].length) {
arr.push({ ...obj,
content: removeObjects(obj["content"])
})
return arr
} else {
arr.push(obj);
return arr;
}
}, []);
}
console.log(removeObjects(content))
Expected output:
const content = [{
prop1: "someValue",
prop2: "someValue",
content: [
{
prop1: "someValue",
prop3: "someValue",
myProperty: "otherValue"
}
]
},
{
prop5: "someValue",
prop2: "someValue"
}
]
You can use below function to get expected result:
let data = {
content: [
{
prop1: 'someValue',
prop2: 'someValue',
content: [
{
prop2: 'someValue',
prop3: 'someValue',
myProperty: 'myValue'
},
{
prop1: 'someValue',
prop3: 'someValue',
myProperty: 'otherValue'
}
]
},
{
prop5: 'someValue',
prop2: 'someValue'
}
]
}
function removeMyvalyeObj(data) {
for (let i = data.content.length - 1; i >= 0; i--) {
if (data.content[i].myProperty === 'myValue') {
data.content.splice(i, 1);
} else if(data.content[i].content) {
removeMyvalyeObj(data.content[i])
}
}
}
removeMyvalyeObj(data);
console.log(data);
Try this, (this is JavaScript version)
let someValue = 'a';
let otherValue ='x';
let myValue = 'xy';
let content = [
{
prop1: someValue,
prop2: someValue,
content: [
{
prop2: someValue,
prop3: someValue,
myProperty: myValue
},
{
prop1: someValue,
prop3: someValue,
myProperty: otherValue
}
]
},
{
prop5: someValue,
prop2: someValue
}
];
function removeObjects(content, values) {
debugger;
if (!content || content.length === 0) {
return
}
content = content.filter((c) => {
if (!c.myProperty) return true
return c.myProperty.indexOf(values) > 0
})
// Here is my problem since I am supposed to do a recursive call on each of child contents,
// how do I merge back the original array?
return content.map(x => ({
...x,
content: removeObjects(x.content, values)
}))
}
console.log(removeObjects(content, 'x'))
Use recursive approach, to have filter array of items and their content array.
const filter = (arr, name, value) =>
arr
.filter((obj) => !(name in obj && obj[name] === value))
.map((obj) => {
const nObj = { ...obj };
if (obj.content && Array.isArray(obj.content)) {
nObj.content = filter(obj.content, name, value);
}
return nObj;
});
content = [
{
prop1: 'someValue',
prop2: 'someValue',
content: [
{
prop2: 'someValue',
prop3: 'someValue',
myProperty: 'myValue',
},
{
prop1: 'someValue',
prop3: 'someValue',
myProperty: 'otherValue',
},
],
},
{
prop5: 'someValue',
prop2: 'someValue',
},
];
const updated = filter(content, "myProperty", "myValue");
console.log(updated);

How to access the keys of an indexed array

Say I have the array below, and I want to retrieve the keys inside the indexed object, so that I would end up with an array like this ["prop1", "prop2", "prop3", "prop3"]
const testArray =  [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
Using reduce and concat:
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
const keys = testArray.reduce((arr, curr, index) =>
arr.concat(Object.keys(curr[index])),[])
console.log(keys)
// ["prop1", "prop2", "prop3", "prop4"]
You can map over the array, taking the value's keys
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
const keys = testArray
.map(e => Object.keys(...Object.values(e)))
.flat();
console.log(keys);
Note that flat() is experimental for now, so you might want to use reduce or so to flatten the array...
#bambam has given the solution with "map", which is clearer.
You can also do that with "reduce" as below.
testArray.reduce((arr, d) =>
(arr.push(
Object.entries(d).map(([, l]) => Object.keys(l))
)
, arr)
,[]).flat(2)
One solution with forEach
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
];
var arr = [];
testArray.forEach((data, index) => {
let tempArr = Object.keys(data[index]);
arr = arr.concat(tempArr);
})
console.log(arr);
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
];
var arr = testArray.map((p)=>{return Object.keys(p[Object.keys(p)[0]]);});
console.log([].concat.apply([],arr));

Filter array of objects whose any properties contains a value

I'm wondering what is the cleanest way, better way to filter an array of objects depending on a string keyword. The search has to be made in any properties of the object.
When I type lea I want to go trough all the objects and all their properties to return the objects that contain lea
When I type italy I want to go trough all the objects and all their properties to return the objects that contain italy.
I know there are lot of solutions but so far I just saw some for which you need to specify the property you want to match.
ES6 and lodash are welcome!
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
filterByValue(arrayOfObject, 'lea') // => [{name: 'Lea',country: 'Italy'}]
filterByValue(arrayOfObject, 'ita') // => [{name: 'Lea',country: 'Italy'}, {name: 'John',country: 'Italy'}]
You could filter it and search just for one occurence of the search string.
Methods used:
Array#filter, just for filtering an array with conditions,
Object.keys for getting all property names of the object,
Array#some for iterating the keys and exit loop if found,
String#toLowerCase for getting comparable values,
String#includes for checking two string, if one contains the other.
function filterByValue(array, string) {
return array.filter(o =>
Object.keys(o).some(k => o[k].toLowerCase().includes(string.toLowerCase())));
}
const arrayOfObject = [{ name: 'Paul', country: 'Canada', }, { name: 'Lea', country: 'Italy', }, { name: 'John', country: 'Italy' }];
console.log(filterByValue(arrayOfObject, 'lea')); // [{name: 'Lea', country: 'Italy'}]
console.log(filterByValue(arrayOfObject, 'ita')); // [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
.as-console-wrapper { max-height: 100% !important; top: 0; }
Well when we already know that its not going to be a search on an object with methods, we can do the following for saving bit on time complexity :
function filterByValue(array, value) {
return array.filter((data) => JSON.stringify(data).toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
Use Object.keys to loop through the properties of the object. Use reduce and filter to make the code more efficient:
const results = arrayOfObject.filter((obj)=>{
return Object.keys(obj).reduce((acc, curr)=>{
return acc || obj[curr].toLowerCase().includes(term);
}, false);
});
Where term is your search term.
You can always use array.filter() and then loop through each object and if any of the values match the value you are looking for, return that object.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
let lea = arrayOfObject.filter(function(obj){
//loop through each object
for(key in obj){
//check if object value contains value you are looking for
if(obj[key].includes('Lea')){
//add this object to the filtered array
return obj;
}
}
});
console.log(lea);
This code checks all the nested values until it finds what it's looking for, then returns true to the "array.filter" for the object it was searching inside(unless it can't find anything - returns false). When true is returned, the object is added to the array that the "array.filter" method returns. When multiple keywords are entered(spaced out by a comma and a space), the search is narrowed further, making it easier for the user to search for something.
Stackblitz example
const data = [
{
a: 'aaaaaa',
b: {
c: 'c',
d: {
e: 'e',
f: [
'g',
{
i: 'iaaaaaa',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'cccccc',
d: {
e: 'e',
f: [
'g',
{
i: 'icccccc',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'c',
d: {
e: 'eeeeee',
f: [
'g',
{
i: 'ieeeeee',
j: {},
k: [],
},
],
},
},
},
];
function filterData(data, filterValues) {
return data.filter((value) => {
return filterValues.trim().split(', ').every((filterValue) => checkValue(value, filterValue));
});
}
function checkValue(value, filterValue) {
if (typeof value === 'string') {
return value.toLowerCase().includes(filterValue.toLowerCase());
} else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
if (Array.isArray(value)) {
return value.some((v) => checkValue(v, filterValue));
} else {
return Object.values(value).some((v) => checkValue(v, filterValue));
}
} else {
return false;
}
}
console.log(filterData(data, 'a, c'));
console.log(filterData(data, 'a, c, ic'));
One way would be to use Array#filter, String#toLowerCase and String#indexOf like below.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}];
function filterByValue(arrayOfObject, term) {
var ans = arrayOfObject.filter(function(v,i) {
if(v.name.toLowerCase().indexOf(term) >=0 || v.country.toLowerCase().indexOf(term) >=0) {
return true;
} else false;
});
console.log( ans);
}
filterByValue(arrayOfObject, 'ita');
function filterByValue(arrayOfObject,words){
let reg = new RegExp(words,'i');
return arrayOfObject.filter((item)=>{
let flag = false;
for(prop in item){
if(reg.test(prop)){
flag = true;
}
}
return flag;
});
}
Here's how I would do it using lodash:
const filterByValue = (coll, value) =>
_.filter(coll, _.flow(
_.values,
_.partialRight(_.some, _.method('match', new RegExp(value, 'i')))
));
filterByValue(arrayOfObject, 'lea');
filterByValue(arrayOfObject, 'ita');
Here is a version of the above which filters by a value which is derived from an array of object's property. The function takes in the array of objects and the specified array of object's property key.
// fake ads list with id and img properties
const ads = [{
adImg: 'https://test.com/test.png',
adId: '1'
}, {
adImg: 'https://test.com/test.png',
adId: '2'
}, {
adImg: 'https://test.com/test.png',
adId: '3'
}, {
adImg: 'https://test.com/test-2.png',
adId: '4'
}, {
adImg: 'https://test.com/test-2.png',
adId: '5'
}, {
adImg: 'https://test.com/test-3.png',
adId: '6'
}, {
adImg: 'https://test.com/test.png',
adId: '7'
}, {
adImg: 'https://test.com/test-6.png',
adId: '1'
}];
// function takes arr of objects and object property
// convert arr of objects to arr of filter prop values
const filterUniqueItemsByProp = (arrOfObjects, objPropFilter) => {
return arrOfObjects.filter((item, i, arr) => {
return arr.map(prop => prop[objPropFilter]).indexOf(item[objPropFilter]) === i;
});
};
const filteredUniqueItemsByProp = filterUniqueItemsByProp(ads, 'adImg');
console.log(filteredUniqueItemsByProp);

Categories

Resources