JavaScript: filter() for Objects
Solution 1
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 2
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.
Solution 3
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}
Solution 4
If you're willing to use underscore or lodash, you can use pick
(or its opposite, omit
).
Examples from underscore's docs:
_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
// {name: 'moe', age: 50}
Or with a callback (for lodash, use pickBy):
_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
return _.isNumber(value);
});
// {age: 50}
Solution 5
ES6 approach...
Imagine you have this object below:
const developers = {
1: {
id: 1,
name: "Brendan",
family: "Eich"
},
2: {
id: 2,
name: "John",
family: "Resig"
},
3: {
id: 3,
name: "Alireza",
family: "Dezfoolian"
}
};
Create a function:
const filterObject = (obj, filter, filterValue) =>
Object.keys(obj).reduce((acc, val) =>
(obj[val][filter] === filterValue ? acc : {
...acc,
[val]: obj[val]
}
), {});
And call it:
filterObject(developers, "name", "Alireza");
and will return:
{
1: {
id: 1,
name: "Brendan",
family: "Eich"
},
2: {
id: 2,
name: "John",
family: "Resig"
}
}
Related videos on Youtube
AgileMeansDoAsLittleAsPossible
Updated on March 28, 2022Comments
-
AgileMeansDoAsLittleAsPossible about 2 years
ECMAScript 5 has the
filter()
prototype forArray
types, but notObject
types, if I understand correctly.How would I implement a
filter()
forObject
s in JavaScript?Let's say I have this object:
var foo = { bar: "Yes" };
And I want to write a
filter()
that works onObject
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>
-
NT3RP about 13 yearsWhat errors do you get, specifically?
-
Shoaib about 13 yearsWhat are the errors you're getting? Post them if possible :)
-
Crescent Fresh about 13 yearsThere's a bit of ambiguous history wrt jQuery and scripts that extend
Object.prototype
: bugs.jquery.com/ticket/2721 -
NoxFly about 4 yearsexactly what I needed, except that you must remove the "!" in the !predicate(this[key]) to have the real filter method.
-
DanteTheSmith over 2 yearsThose even mentioning extending the Object prototype, who are not interns, get my recommendation to get immediately fired. There is NO USE CASE where that is a desirable solution. You are basically rewriteing the language with that, since you took the one thing everything in JS is and said: let me make that a little bit different. Yes the language lets you mess with its highest prototype but you should really know better. To make you an analogy - imagine if you took your car and made it a little bit different. Breaks are no longer working if speed is between 59 and 60.
-
-
Martin Jespersen about 13 years@patrick: you might wish to give a reason, so it is slightly more informative :P
-
user113716 about 13 years@Martin: I'm hoping to appeal to OP's fear of the unknown. ;o)
-
Martin Jespersen about 13 years@patrick: give a man a bread and you'll feed him for a day, teach him how to bake and you'll feed him for a lifetime (or something, I'm danish, I don't know the correct English sayings ;)
-
user113716 about 13 years@Dykam: Yeah, I originally saw the question as why is this breaking my code. Updated with a solution.
-
user113716 about 13 years@Martin: Make him afraid of heights, and he'll never die in a plane crash. ;o)
-
Christian C. Salvadó about 13 years@patrick, don't forget to declare the
key
variable in thefor-in
loop ;) -
user113716 about 13 years@CMS: Ugh, you caught me on that again! I'm not sure how many more times I'll be able to use the copy/paste defense. :o) EDIT: Fixed. And thanks. :o)
-
pyrotechnick over 12 yearsYou're doing it wrong... !predicate(obj[key]) should be predicate(obj[key])
-
user113716 over 12 years@pyrotechnick: No. First, the main point of the answer is to not extend
Object.prototype
, but rather to just place the function onObject
. Second, this is the OP's code. Clearly OP's intention is to have.filter()
be such that it filters out the positive results. In other words, it is a negative filter, where a positive return value means it is excluded from the result. If you look at the jsFiddle example, he's filtering out existing properties that areundefined
. -
pyrotechnick over 12 years@patrick dw: No. First, I didn't mention extending/not extending prototypes. Second, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… -- "Creates a new array with all elements that pass the test implemented by the provided function." Implementing the exact opposite on a global seems pretty silly, doesn't it?
-
user113716 over 12 years@pyrotechnick: That's right, you didn't mention extending/not extending prototypes, and that's my point. You said I'm doing it wrong, but the only "it" I'm doing is telling OP to not extend
Object.prototype
. From the question: "This works..., but when I add it to my site..., I get JavaScript errors" If OP decides to implement a.filter()
with the opposite behavior of that ofArray.prototpe.filter
, that's up to him/her. Please leave a comment under the question if you want to notify OP that the code is wrong, but don't tell me that I'm doing it wrong when it isn't my code. -
hackp0int over 7 yearsCould it be more complex query? For example:
x => x.Expression.Filters.Filter
-
trincot over 7 years@IamStalker, did you try? It does not matter, as long as you provide a valid function in the second argument. NB: I have no idea what
.Filter
is at the end, but if it is a function, you need to call it (x => x.Expression.Filters.Filter()
) -
Abdennour TOUMI over 7 yearsso long! can be done on 1 line . check this
-
trincot over 7 yearsThis does not use a given predicate function as is required by the question.
-
RobG about 7 yearsNewer features may make for less code, but they also make for slower performance. Ed 3 compliant code runs more than twice as fast in most browsers.
-
kristianp over 6 yearsWhat's the point of having to call this with Object.filter(...)? Is that some kind of coding convention? It doesn't need to be a function on Object.
-
mibbit over 6 yearslodash is a bad solution because filtering for empty objects will also remove numbers.
-
Ben Carp almost 6 yearsCan someone please explain the pattern
for (key in obj){if obj.hasOwnProperty(key){....}}
AFAIK, every key in this for loop is an enumerable property of obj, and hence obj.hasOwnProperty will always return true. What am I missing? In which cases will it return false? -
Bernardo Dal Corno over 5 years
-
Oliver Joseph Ash over 5 yearsTypeScript version of the last variant: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
-
Pille about 5 yearsLooks good! But why does it return just the other object (and not the one with the name/filterValue of "Alireza")?
-
trincot about 5 years
$.map
can take an object, but it returns an array, so the original property names are lost. The OP needs a filtered plain object. -
trincot about 5 years@Pille, the OP asked for it to be like that (note the negative filter with
!predicate
in their own code). -
Mr. Polywhirl about 5 years@trincot Thanks, I updated the response to return a copy of the object, rather than an in-place return.
-
Emobe about 5 years@BenCarp objects come with other properties not defined by the user, built-in ones so its just to avoid those
-
Ben White almost 5 yearsTo more closely mimic the Array.filter method, I would suggest using the following arguments when calling the predicate (value, propertyName, object)
predicate(obj[key], key, obj)
-
Ira Herman over 4 yearsI just used lodash for this and it's a great solution. Thanks @Bogdan D!
-
Admin over 4 years@MartinJespersen i don't think the saying needs further translation, it is very accurate the way it is.
-
FabricioG about 4 yearsThis can be a question in itself. Which of these should I use to get a value in an object :)
-
trincot about 4 years@Fabricio, to get a "value in an object", you just do
object[key] = value
. Yes, if you are stuck on a coding problem, then please ask a new question. If you want to have info about efficiency, good coding style, for working code, then check out CodeReview -
Bogdan D about 4 years@mibbit can you be more specific? I believe it's just a matter of implementing the callback correctly
-
Justin almost 4 years@MartinJespersen It's "give a man a fish; feed him for a day. Teach a man to fish; feed him for a lifetime". My favorite variation: "Give a man a fire; he'll be warm for a day; Set a man on fire; he'll be warm for the rest of his lifetime"
-
Erik Aronesty almost 4 years+1 for answering the question while also explaining why not to. Someone might want to extend objects (a test suite that checks for people doing bad things?), which I did. So many SO answers tell you not to do something while forgetting to tell you how to do it.
-
Slavik Meltser almost 4 yearsGreat job! Thank you for these great explanations and examples.
-
Victor over 3 yearsAwesome solution! Breaking an Object into
entries
, filtering by desired conditions, then create a new Object withfromEntries
. -
Victor over 3 yearsI used that solution to filter the object by multiple keys via
includes
.Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => ['a', 'b', 'c', 'd', 'e'].includes(key)))
-
Victor over 3 yearsI have no doubts this is the best solution of all. Congratulations. My favorite solution is the combination with
entries
andfromEntries
. So simple, yet so readable, comprehensible, powerful and a higher-level abstraction! -
trincot over 3 yearsNot sure why you repeat an answer that was given more than a year earlier
-
SamB over 3 years@user113716, I believe pyrotechnick is right, in a sense. Sure, you answered OP's question, but as it turns out, this is the top answer to a SO question with 424k views. The fact is, most people are going to come here looking for a Object.filter function, not what you have written. I came back to write this comment because the filter function I copied from your answer wasn't working and I imagine that isn't the first time that has happened. Regardless, thank you for writing such a well thought out response to OPs question.
-
Ciaran Gallagher almost 3 yearsSolution 3 fails for me if there are null values in the object, not exactly sure how I should deal with that
-
trincot almost 3 years@CiaranGallagher, when I add
other: null
as extra property in the sample object, then snippet #3 still runs fine. I suppose your filter condition is checking a property of each value, which only makes sense if your values are notnull
. You can use the??
operator in your filter condition if that is your case. But this has little to do with the answer here, but more with providing a solid filter condition. -
lowcrawler over 2 yearsHow would one put solution #1 into a utility file and export/import it?
-
trincot over 2 years@lowcrawler, there is nothing to export, as this mutates the global
Object
variable. There is noexport
needed, and you can just doimport 'myfile'
without assignment. -
Rasmus over 2 yearsFor anyone using multidimensional objects, change '=> value === 5' to '=> value.secondaryValue === 5'