JavaScript: filter() for Objects

642,804

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:

  1. Using reduce and Object.keys
  2. As (1), in combination with Object.assign
  3. Using map and spread syntax instead of reduce
  4. Using Object.entries and Object.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"
  }
}
Share:
642,804

Related videos on Youtube

AgileMeansDoAsLittleAsPossible
Author by

AgileMeansDoAsLittleAsPossible

Updated on March 28, 2022

Comments

  • AgileMeansDoAsLittleAsPossible
    AgileMeansDoAsLittleAsPossible about 2 years

    ECMAScript 5 has the filter() prototype for Array types, but not Object types, if I understand correctly.

    How would I implement a filter() for Objects in JavaScript?

    Let's say I have this object:

    var foo = {
        bar: "Yes"
    };
    

    And I want to write a filter() that works on Objects:

    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
      NT3RP about 13 years
      What errors do you get, specifically?
    • Shoaib
      Shoaib about 13 years
      What are the errors you're getting? Post them if possible :)
    • Crescent Fresh
      Crescent Fresh about 13 years
      There's a bit of ambiguous history wrt jQuery and scripts that extend Object.prototype: bugs.jquery.com/ticket/2721
    • NoxFly
      NoxFly about 4 years
      exactly what I needed, except that you must remove the "!" in the !predicate(this[key]) to have the real filter method.
    • DanteTheSmith
      DanteTheSmith over 2 years
      Those 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
    Martin Jespersen about 13 years
    @patrick: you might wish to give a reason, so it is slightly more informative :P
  • user113716
    user113716 about 13 years
    @Martin: I'm hoping to appeal to OP's fear of the unknown. ;o)
  • Martin Jespersen
    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
    user113716 about 13 years
    @Dykam: Yeah, I originally saw the question as why is this breaking my code. Updated with a solution.
  • user113716
    user113716 about 13 years
    @Martin: Make him afraid of heights, and he'll never die in a plane crash. ;o)
  • Christian C. Salvadó
    Christian C. Salvadó about 13 years
    @patrick, don't forget to declare the key variable in the for-in loop ;)
  • user113716
    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
    pyrotechnick over 12 years
    You're doing it wrong... !predicate(obj[key]) should be predicate(obj[key])
  • user113716
    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 on Object. 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 are undefined.
  • pyrotechnick
    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
    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 of Array.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
    hackp0int over 7 years
    Could it be more complex query? For example: x => x.Expression.Filters.Filter
  • trincot
    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
    Abdennour TOUMI over 7 years
    so long! can be done on 1 line . check this
  • trincot
    trincot over 7 years
    This does not use a given predicate function as is required by the question.
  • RobG
    RobG about 7 years
    Newer 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
    kristianp over 6 years
    What'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
    mibbit over 6 years
    lodash is a bad solution because filtering for empty objects will also remove numbers.
  • Ben Carp
    Ben Carp almost 6 years
    Can 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
    Bernardo Dal Corno over 5 years
    Please check my vanilla answer and gist
  • Oliver Joseph Ash
    Oliver Joseph Ash over 5 years
    TypeScript version of the last variant: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
  • Pille
    Pille about 5 years
    Looks good! But why does it return just the other object (and not the one with the name/filterValue of "Alireza")?
  • trincot
    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
    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
    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
    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
    Ben White almost 5 years
    To 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
    Ira Herman over 4 years
    I just used lodash for this and it's a great solution. Thanks @Bogdan D!
  • Admin
    Admin over 4 years
    @MartinJespersen i don't think the saying needs further translation, it is very accurate the way it is.
  • FabricioG
    FabricioG about 4 years
    This can be a question in itself. Which of these should I use to get a value in an object :)
  • trincot
    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
    Bogdan D about 4 years
    @mibbit can you be more specific? I believe it's just a matter of implementing the callback correctly
  • Justin
    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
    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
    Slavik Meltser almost 4 years
    Great job! Thank you for these great explanations and examples.
  • Victor
    Victor over 3 years
    Awesome solution! Breaking an Object into entries, filtering by desired conditions, then create a new Object with fromEntries.
  • Victor
    Victor over 3 years
    I 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
    Victor over 3 years
    I have no doubts this is the best solution of all. Congratulations. My favorite solution is the combination with entries and fromEntries. So simple, yet so readable, comprehensible, powerful and a higher-level abstraction!
  • trincot
    trincot over 3 years
    Not sure why you repeat an answer that was given more than a year earlier
  • SamB
    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
    Ciaran Gallagher almost 3 years
    Solution 3 fails for me if there are null values in the object, not exactly sure how I should deal with that
  • trincot
    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 not null. 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
    lowcrawler over 2 years
    How would one put solution #1 into a utility file and export/import it?
  • trincot
    trincot over 2 years
    @lowcrawler, there is nothing to export, as this mutates the global Object variable. There is no export needed, and you can just do import 'myfile' without assignment.
  • Rasmus
    Rasmus over 2 years
    For anyone using multidimensional objects, change '=> value === 5' to '=> value.secondaryValue === 5'