web-dev-qa-db-fra.com

JavaScript: filtre () pour les objets

ECMAScript 5 a le prototype filter() pour les types Array, mais pas les types Object, si je comprends bien.

Comment implémenter une filter() pour Objects en JavaScript? 

Disons que j'ai cet objet:

var foo = {
    bar: "Yes"
};

Et je veux écrire une filter() qui fonctionne sur 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;
};

Cela fonctionne lorsque je l'utilise dans la démo suivante, mais lorsque je l'ajoute à mon site utilisant jQuery 1.5 et jQuery UI 1.8.9, des erreurs JavaScript se produisent dans 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>

Ne jamais étendre Object.prototype.

Des choses horribles vont arriver à votre code. Les choses vont casser. Vous élargissez tous types d'objets, y compris les littéraux d'objet.

Voici un exemple rapide que vous pouvez essayer:

    // 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!"

Au lieu de cela, créez une fonction que vous transmettez à l'objet.

Object.filter = function( obj, predicate) {
    var result = {}, key;
    // ---------------^---- as noted by @CMS, 
    //      always declare variables with the "var" keyword

    for (key in obj) {
        if (obj.hasOwnProperty(key) && !predicate(obj[key])) {
            result[key] = obj[key];
        }
    }

    return result;
};
134
user113716

Tout d’abord, il est considéré comme une mauvaise pratique d’étendre Object.prototype . Indiquez plutôt votre fonction en tant que fonction utilitaire sur Object, comme il en existe déjà Object.keys, Object.assign, Object.is, ... etc.

Je propose ici plusieurs solutions:

  1. Utiliser reduce et Object.keys
  2. As (1), en combinaison avec Object.assign
  3. Utiliser la variable map et spread au lieu de la variable reduce
  4. Utiliser Object.entries et Object.fromEntries

1. Utilisation de reduce et Object.keys

Avec reduce _ et Object.keys pour implémenter le filtre souhaité (à l'aide de ES6 syntaxe de flèche _):

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);

Notez que, dans le code ci-dessus, predicate doit être une condition inclusion (contrairement à la condition exclusion utilisée par le OP), de sorte qu'elle corresponde à la façon dont fonctionne Array.prototype.filter .

2. Comme (1), en combinaison avec Object.assign

Dans la solution ci-dessus, le opérateur de virgule est utilisé dans la partie reduce pour renvoyer l'objet res muté. Cela pourrait bien sûr être écrit comme deux déclarations au lieu d'une expression, mais cette dernière est plus concise. Pour le faire sans l'opérateur de virgule, vous pouvez utiliser à la place Object.assign , lequel ne renvoie l'objet muté:

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. Utiliser la variable map et spread au lieu de la variable reduce

Ici, nous déplaçons l'appel Object.assign hors de la boucle pour qu'il ne soit créé qu'une seule fois et lui transmettons les clés individuelles sous forme d'arguments séparés (à l'aide de la syntaxe propagation ):

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. Utiliser Object.entries et Object.fromEntries

Lorsque la solution convertit l'objet en tableau intermédiaire, puis le reconvertit en objet simple, il serait utile d'utiliser Object.entries (ES2017) et d'utiliser une méthode qui fait l'inverse (c'est-à-dire crée un objet à partir d'un tableau de paires clé/valeur ). Au moment de la rédaction de cet article, la Object.fromEntries proposition est à l'étape 3 et Firefox a le { mis en œuvre } _. Sinon, un polyfill peut être utilisé.

Cela conduit à ces deux méthodes "one-liner" sur Object (y compris le polyfill):

Object.fromEntries = arr => Object.assign({}, ...arr.map( ([k, v]) => ({[k]: v}) ));
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);

La fonction de prédicat obtient ici une paire clé/valeur en tant qu'argument, ce qui est un peu différent, mais laisse davantage de possibilités dans la logique de la fonction de prédicat.

153
trincot

Si vous souhaitez utiliser underscore ou lodash, vous pouvez utiliser pick (ou son contraire, omit ).

Exemples tirés de la documentation de soulignement:

_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
// {name: 'moe', age: 50}

Ou avec un rappel (pour lodash, utilisez pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}
18
Bogdan D

Comme Patrick l'a déjà dit, c'est une mauvaise idée, car cela briserait certainement tout code tiers que vous pourriez souhaiter utiliser. 

Toutes les bibliothèques telles que jquery ou prototype se briseront si vous étendez Object.prototype. La raison en est que l'itération paresseuse sur les objets (sans vérifications hasOwnProperty) sera interrompue puisque les fonctions que vous ajoutez feront partie de l'itération.

4
Martin Jespersen

J'ai créé un Object.filter() qui non seulement filtre par une fonction, mais accepte également un tableau de clés à inclure. Le troisième paramètre facultatif vous permettra d’inverser le filtre.

Donné:

var foo = {
    x: 1,
    y: 0,
    z: -1,
    a: 'Hello',
    b: 'World'
}

Tableau:

Object.filter(foo, ['z', 'a', 'b'], true);

Une fonction:

Object.filter(foo, function (key, value) {
    return Ext.isString(value);
});

Code

Disclaimer : J'ai choisi d'utiliser Ext JS core par souci de concision. Je ne pensais pas qu'il était nécessaire d'écrire des vérificateurs de type pour les types d'objet car cela ne faisait pas partie de la question

// Helper functions
function clone(obj) { return JSON.parse(JSON.stringify(obj)); }
function print(obj) {
    document.getElementById('disp').innerHTML += JSON.stringify(obj, undefined, '  ') + '<br />';
    console.log(obj);
}

Object.filter = function (obj, ignore, invert) {
    if (ignore === undefined) {
        return obj;   
    }
    invert = invert || false;
    var not = function(condition, yes) { return yes ? !condition : condition; };
    var isArray = Ext.isArray(ignore);
    for (var key in obj) {
        if (obj.hasOwnProperty(key) &&
                (isArray && not(!Ext.Array.contains(ignore, key), invert)) ||
                (!isArray && not(!ignore.call(undefined, key, obj[key]), invert))) {
            delete obj[key];
        }
    }
    return obj;
};

var foo = {
    x: 1,
    y: 0,
    z: -1,
    a: 'Hello',
    b: 'World'
}, bar = clone(foo);

print(Object.filter(foo, ['z', 'a', 'b'], true));
print(Object.filter(bar, function (key, value) {
    return Ext.isString(value);
}));
#disp {
    white-space: pre;
    font-family: monospace
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/extjs/4.2.1/builds/ext-core.min.js"></script>
<div id="disp"></div>

2
Mr. Polywhirl

Donné

object = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};

keys = ['firstname', 'age'];

puis :

keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
// {firstname:'abd', age: 16}

// Helper
function filter(object, ...keys) {
  return keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
  
};

//Example
const person = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};

// Expected to pick only firstname and age keys
console.log(
  filter(person, 'firstname', 'age')
)

2
Abdennour TOUMI

Que diriez-vous:

function filterObj(keys, obj) {
  const newObj = {};
  for (let key in obj) {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

Ou...

function filterObj(keys, obj) {
  const newObj = {};
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  });
  return newObj;
}
1
shaunw

Comme tout le monde le dit, ne vissez pas avec prototype. Au lieu de cela, écrivez simplement une fonction pour le faire. Voici ma version avec lodash:

import each from 'lodash/each';
import get from 'lodash/get';

const myFilteredResults = results => {
  const filteredResults = [];

  each(results, obj => {
    // filter by whatever logic you want.

    // sample example
    const someBoolean = get(obj, 'some_boolean', '');

    if (someBoolean) {
      filteredResults.Push(obj);
    }
  });

  return filteredResults;
};
0
saran3h

ES6 approche ...

Imaginez que vous avez cet objet ci-dessous:

const developers = {
  1: {
   id: 1,
   name: "Brendan", 
   family: "Eich"
  },   
  2: {
   id: 2,
   name: "Alireza", 
   family: "Dezfoolian"
 }
};

Créer une fonction:

const filterObject = (obj, filter, filterValue) => 
   Object.keys(obj).reduce((acc, val) => 
   (obj[val][filter] === filterValue ? acc : {
       ...acc,
       [val]: obj[val]
   }                                        
), {});

Et appelez ça:

filterObject(developers, "name", "Alireza");

et va retournera :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
}
0
Alireza

Dans ces cas, j'utilise jquery $ .map, qui peut gérer des objets. Comme indiqué dans d'autres réponses, il n'est pas conseillé de modifier les prototypes natifs ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

Vous trouverez ci-dessous un exemple de filtrage en vérifiant simplement une propriété de votre objet. Il retourne le propre objet si votre condition est vraie ou renvoie undefined sinon. La propriété undefined fera disparaître cet enregistrement de votre liste d'objets; 

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});
0
Marcel Kohls

Ma solution avisée:

function objFilter(obj, filter, nonstrict){
  r = {}
  if (!filter) return {}
  if (typeof filter == 'string') return {[filter]: obj[filter]}
  for (p in obj) {
    if (typeof filter == 'object' &&  nonstrict && obj[p] ==  filter[p]) r[p] = obj[p]
    else if (typeof filter == 'object' && !nonstrict && obj[p] === filter[p]) r[p] = obj[p]
    else if (typeof filter == 'function'){ if (filter(obj[p],p,obj)) r[p] = obj[p]}
    else if (filter.length && filter.includes(p)) r[p] = obj[p]
  }
  return r
}

Cas de test:

obj = {a:1, b:2, c:3}

objFilter(obj, 'a') // returns: {a: 1}
objFilter(obj, ['a','b']) // returns: {a: 1, b: 2}
objFilter(obj, {a:1}) // returns: {a: 1}
objFilter(obj, {'a':'1'}, true) // returns: {a: 1}
objFilter(obj, (v,k,o) => v%2===1) // returns: {a: 1, c: 3}

https://Gist.github.com/khullah/872d5a174108823159d845cc5baba337

0
Z. Khullah