JavaScript: filtre () pour les objets


187

ECMAScript 5 a le filter()prototype pour les Arraytypes, mais pas pour les Objecttypes, si je comprends bien.

Comment implémenter un filter()for Objects en JavaScript?

Disons que j'ai cet objet:

var foo = {
    bar: "Yes"
};

Et je veux écrire un 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 qui utilise jQuery 1.5 et jQuery UI 1.8.9, j'obtiens des erreurs JavaScript dans FireBug.


Quelles erreurs obtenez-vous, en particulier?
NT3RP

Quelles erreurs obtenez-vous? Postez-les si possible :)
Zack The Human

Il y a un peu d'histoire ambiguë pour jQuery et des scripts qui s'étendent Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh

exactement ce dont j'avais besoin, sauf que vous devez supprimer le "!" dans le prédicat! (cette [clé]) pour avoir la vraie méthode de filtrage.
NoxFly

Réponses:


201

Jamais jamais prolonger Object.prototype.

Des choses horribles vont arriver à votre code. Les choses vont casser. Vous étendez tous les 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 passez l'objet.

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

61
@patrick: donnez un pain à un homme et vous le nourrirez pendant un jour, apprenez-lui à cuisiner et vous le nourrirez toute une vie (ou quelque chose comme ça, je suis danois, je ne connais pas les bons dictons anglais ;)
Martin Jespersen

13
Vous vous trompez ...! Predicate (obj [key]) should be predicate (obj [key])
pyrotechnick

6
@pyrotechnick: Non. Premièrement, le point principal de la réponse est de ne pas étendre Object.prototype, mais plutôt de simplement activer la fonction Object. Deuxièmement, c'est le code de l'OP . Il est clair que l'intention d'OP est d' .filter()être telle qu'elle filtre les résultats positifs. En d'autres termes, il s'agit d'un filtre négatif, où une valeur de retour positive signifie qu'il est exclu du résultat. Si vous regardez l'exemple jsFiddle, il filtre les propriétés existantes qui le sont undefined.
user113716

4
@patrick dw: Non. Premièrement, je n'ai pas mentionné l'extension / non extension des prototypes. Deuxièmement, developer.mozilla.org/en/JavaScript/Reference/Global_Objects /... - "Crée un nouveau tableau avec tous les éléments qui passent le test implémenté par la fonction fournie." Mettre en œuvre exactement le contraire sur un plan mondial semble assez idiot, n'est-ce pas?
pyrotechnick

8
@pyrotechnick: C'est vrai, vous n'avez pas mentionné l'extension / non extension des prototypes, et c'est ce que je veux dire. Vous avez dit que je faisais mal, mais le seul "ça" que je fais est de dire à OP de ne pas prolonger Object.prototype. De la question: "Cela fonctionne ..., mais quand je l'ajoute à mon site ..., j'obtiens des erreurs JavaScript" Si OP décide d'implémenter un .filter()avec le comportement opposé de celui de Array.prototpe.filter, c'est à lui / elle. Veuillez laisser un commentaire sous la question si vous souhaitez informer OP que le code est erroné, mais ne me dites pas que je le fais mal alors que ce n'est pas mon code.
user113716

284

Tout d'abord, il est considéré comme une mauvaise pratique de s'étendreObject.prototype . Au lieu de cela, indiquez votre fonction en fonction d'utilité sur Object, comme il y a déjà Object.keys, Object.assign, Object.is, ... etc.

Je propose ici plusieurs solutions:

  1. Utilisation reduceetObject.keys
  2. As (1), en combinaison avec Object.assign
  3. Utiliser mapet diffuser la syntaxe au lieu dereduce
  4. Utilisation Object.entriesetObject.fromEntries

1. Utilisation reduceetObject.keys

Avec reduceet Object.keyspour implémenter le filtre souhaité (en utilisant la syntaxe des flèches ES6 ):

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 predicatedoit être une condition d' inclusion (contrairement à la condition d' exclusion utilisée par l'OP), afin qu'elle corresponde à la façon dont Array.prototype.filterfonctionne.

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

Dans la solution ci-dessus, l' opérateur virgule est utilisé dans la reducepartie pour renvoyer l' resobjet muté . Cela pourrait bien sûr être écrit comme deux déclarations au lieu d'une seule expression, mais cette dernière est plus concise. Pour le faire sans l'opérateur virgule, vous pouvez utiliser à la Object.assignplace, qui ne retourne 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 mapet diffuser la syntaxe au lieu dereduce

Ici, nous déplaçons l' Object.assignappel hors de la boucle, donc il n'est fait qu'une seule fois, et lui passons les clés individuelles comme arguments séparés (en utilisant la syntaxe de 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. Utilisation Object.entriesetObject.fromEntries

Comme la solution traduit l'objet en un tableau intermédiaire, puis le convertit en un objet simple, il serait utile d'utiliser Object.entries(ES2017) et l'opposé (c'est-à-dire créer un objet à partir d'un tableau de paires clé / valeur ) avec Object.fromEntries( ES2019).

Cela conduit à cette méthode "one-liner" sur 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);

La fonction de prédicat obtient ici une paire clé / valeur comme argument, ce qui est un peu différent, mais permet plus de possibilités dans la logique de la fonction de prédicat.


Cela pourrait-il être une requête plus complexe? Par exemple:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker, avez-vous essayé? Cela n'a pas d'importance, tant que vous fournissez une fonction valide dans le deuxième argument. NB: je n'ai aucune idée de ce qu'il .Filtery a à la fin, mais si c'est une fonction, il faut l'appeler ( x => x.Expression.Filters.Filter())
trincot

1
Les nouvelles fonctionnalités peuvent entraîner moins de code, mais elles ralentissent également les performances . Le code conforme à Ed 3 s'exécute plus de deux fois plus vite dans la plupart des navigateurs.
RobG

1

1
Bon travail! Merci pour ces excellentes explications et exemples.
Slavik Meltser le

21

Si vous êtes prêt à utiliser un trait de soulignement ou un lodash , vous pouvez utiliser pick(ou son contraire omit).

Exemples tirés de la documentation du trait 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}

1
lodash est une mauvaise solution car le filtrage des objets vides supprimera également les nombres.
mibbit

Je viens d'utiliser lodash pour cela et c'est une excellente solution. Merci @Bogdan D!
Ira Herman

@mibbit pouvez-vous être plus précis? Je pense que c'est juste une question d'implémenter correctement le rappel
Bogdan D

11

Approche ES6 ...

Imaginez que vous avez cet objet ci-dessous:

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

Créez une fonction:

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

Et appelez-le:

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

et va reviendra :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}

1
Cela semble bon! Mais pourquoi ne renvoie-t-il que l'autre objet (et pas celui avec le nom / filterValue de "Alireza")?
Pille

1
@Pille, l'OP a demandé que ce soit comme ça (notez le filtre négatif avec !predicatedans leur propre code).
trincot

7

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

Toutes les bibliothèques comme jquery ou prototype se casseront si vous étendez Object.prototype, la raison étant que l'itération paresseuse sur les objets (sans hasOwnPropertyvérifications) se cassera puisque les fonctions que vous ajoutez feront partie de l'itération.


voté pour avoir expliqué le «pourquoi» de cette mauvaise idée de manière claire et concise.
Michael Liquori

5

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

4

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}


3
Cela n'utilise pas une fonction de prédicat donnée comme l'exige la question.
trincot

4

J'ai créé un Object.filter() qui ne filtre pas seulement par une fonction, mais accepte également un tableau de clés à inclure. Le troisième paramètre optionnel 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);

Fonction:

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

Code

Avertissement : j'ai choisi d'utiliser le noyau Ext JS par souci de concision. Je n'ai pas jugé nécessaire d'écrire des vérificateurs de type pour les types d'objet car cela ne faisait pas partie de la question.

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

Object.filter = function (obj, ignore, invert) {
    let result = {}; // Returns a filtered copy of the original list
    if (ignore === undefined) {
        return obj;   
    }
    invert = invert || false;
    let not = function(condition, yes) { return yes ? !condition : condition; };
    let 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))) {
            result[key] = obj[key];
        }
    }
    return result;
};

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

print(Object.filter(foo, ['z', 'a', 'b'], true));
print(Object.filter(foo, (key, value) => 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>


S'il vous plaît vérifier ma réponse vanille et l' essentiel
Z. Khullah

1
@trincot Merci, j'ai mis à jour la réponse pour renvoyer une copie de l'objet, plutôt qu'un retour sur place.
M. Polywhirl

2

Ma solution opiniâtre:

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/bernardoadc/872d5a174108823159d845cc5baba337


2

Solution en Vanilla JS à partir de l'année 2020.


let romNumbers={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}

Vous pouvez filtrer l' romNumbersobjet par clé:

const filteredByKey = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => key === 'I'))
// filteredByKey = {I: 1} 

Ou filtrer l' romNumbersobjet par valeur:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 

1

Si vous souhaitez muter le même objet plutôt que d'en créer un nouveau.

L'exemple suivant supprimera toutes les valeurs 0 ou vides:

const sev = { a: 1, b: 0, c: 3 };
const deleteKeysBy = (obj, predicate) =>
  Object.keys(obj)
    .forEach( (key) => {
      if (predicate(obj[key])) {
        delete(obj[key]);
      }
    });

deleteKeysBy(sev, val => !val);

0

Comme tout le monde l'a dit, ne bousillez pas avec le 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

J'utilise ceci quand j'en ai besoin:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}

-1

Dans ces cas, j'utilise le jquery $ .map, qui peut gérer des objets. Comme mentionné sur d'autres réponses, ce n'est pas une bonne pratique de changer 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 retourne undefinedsinon. La undefinedpropriété fera disparaître cet enregistrement de votre liste d'objets;

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

1
$.mappeut prendre un objet, mais il renvoie un tableau, de sorte que les noms de propriété d'origine sont perdus. L'OP a besoin d'un objet brut filtré.
trincot
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.