JavaScript: filter() for Objects
ECMAScript 5 has the filter()
prototype for Array
types, but not Object
types, if I understand correctly.
How would I implement a filter()
for Object
s in JavaScript?
Let's say I have this object:
var foo = {
bar: "Yes"
};
And I want to write a filter()
that works on Object
s:
Object.prototype.filter = function(predicate) {
var result = {};
for (key in this) {
if (this.hasOwnProperty(key) && !predicate(this[key])) {
result[key] = this[key];
}
}
return result;
};
This works when I use it in the following demo, but when I add it to my site that uses jQuery 1.5 and jQuery UI 1.8.9, I get JavaScript errors in FireBug.
Object.prototype.filter = function(predicate) {
var result = {};
for (key in this) {
if (this.hasOwnProperty(key) && !predicate(this[key])) {
console.log("copying");
result[key] = this[key];
}
}
return result;
};
var foo = {
bar: "Yes",
moo: undefined
};
foo = foo.filter(function(property) {
return typeof property === "undefined";
});
document.getElementById('disp').innerHTML = JSON.stringify(foo, undefined, ' ');
console.log(foo);
#disp {
white-space: pre;
font-family: monospace
}
<div id="disp"></div>
First of all, it's considered bad practice to extend Object.prototype
. Instead, provide your feature as stand-alone function, or if you really want to extend a global, provide it as utility function on Object
, just like there already are Object.keys
, Object.assign
, Object.is
, ...etc.
I provide here several solutions:
- Using
reduce
andObject.keys
- As (1), in combination with
Object.assign
- Using
map
and spread syntax instead ofreduce
- Using
Object.entries
andObject.fromEntries
1. Using reduce
and Object.keys
With reduce
and Object.keys
to implement the desired filter (using ES6 arrow syntax):
Object.filter = (obj, predicate) =>
Object.keys(obj)
.filter( key => predicate(obj[key]) )
.reduce( (res, key) => (res[key] = obj[key], res), {} );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
Note that in the above code predicate
must be an inclusion condition (contrary to the exclusion condition the OP used), so that it is in line with how Array.prototype.filter
works.
2. As (1), in combination with Object.assign
In the above solution the comma operator is used in the reduce
part to return the mutated res
object. This could of course be written as two statements instead of one expression, but the latter is more concise. To do it without the comma operator, you could use Object.assign
instead, which does return the mutated object:
Object.filter = (obj, predicate) =>
Object.keys(obj)
.filter( key => predicate(obj[key]) )
.reduce( (res, key) => Object.assign(res, { [key]: obj[key] }), {} );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
3. Using map
and spread syntax instead of reduce
Here we move the Object.assign
call out of the loop, so it is only made once, and pass it the individual keys as separate arguments (using the spread syntax):
Object.filter = (obj, predicate) =>
Object.assign(...Object.keys(obj)
.filter( key => predicate(obj[key]) )
.map( key => ({ [key]: obj[key] }) ) );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
4. Using Object.entries
and Object.fromEntries
As the solution translates the object to an intermediate array and then converts that back to a plain object, it would be useful to make use of Object.entries
(ES2017) and the opposite (i.e. create an object from an array of key/value pairs) with Object.fromEntries
(ES2019).
It leads to this "one-liner" method on Object
:
Object.filter = (obj, predicate) =>
Object.fromEntries(Object.entries(obj).filter(predicate));
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, ([name, score]) => score > 1);
console.log(filtered);
The predicate function gets a key/value pair as argument here, which is a bit different, but allows for more possibilities in the predicate function's logic.
Never ever extend Object.prototype
.
Horrible things will happen to your code. Things will break. You're extending all object types, including object literals.
Here's a quick example you can try:
// Extend Object.prototype
Object.prototype.extended = "I'm everywhere!";
// See the result
alert( {}.extended ); // "I'm everywhere!"
alert( [].extended ); // "I'm everywhere!"
alert( new Date().extended ); // "I'm everywhere!"
alert( 3..extended ); // "I'm everywhere!"
alert( true.extended ); // "I'm everywhere!"
alert( "here?".extended ); // "I'm everywhere!"
Instead create a function that you pass the object.
Object.filter = function( obj, predicate) {
let result = {}, key;
for (key in obj) {
if (obj.hasOwnProperty(key) && !predicate(obj[key])) {
result[key] = obj[key];
}
}
return result;
};
Solution in Vanilla JS from year 2020.
let romNumbers={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
You can filter romNumbers
object by key:
const filteredByKey = Object.fromEntries(
Object.entries(romNumbers).filter(([key, value]) => key === 'I') )
// filteredByKey = {I: 1}
Or filter romNumbers
object by value:
const filteredByValue = Object.fromEntries(
Object.entries(romNumbers).filter(([key, value]) => value === 5) )
// filteredByValue = {V: 5}