JavaScript: filter () para objetos

187

ECMAScript 5 tiene el filter()prototipo para los Arraytipos, pero no para los Objecttipos, si lo entiendo correctamente.

¿Cómo implementaría un filter()for Objects en JavaScript?

Digamos que tengo este objeto:

var foo = {
    bar: "Yes"
};

Y quiero escribir un filter()que funcione en 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;
};

Esto funciona cuando lo uso en la siguiente demostración, pero cuando lo agrego a mi sitio que usa jQuery 1.5 y jQuery UI 1.8.9, obtengo errores de JavaScript en FireBug.

AgileMeansDoAsLittleAsPossible
fuente
¿Qué errores obtienes, específicamente?
NT3RP
¿Cuáles son los errores que está recibiendo? Publíquelos si es posible :)
Zack The Human
Hay un poco de historia ambigua wrt jQuery y scripts que se extienden Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh
exactamente lo que necesitaba, excepto que debes eliminar el "!" en el predicado! (esta [clave]) para tener el método de filtro real.
NoxFly

Respuestas:

201

Nunca se extienda Object.prototype.

Cosas horribles sucederán con su código. Las cosas se romperán. Estás extendiendo todos los tipos de objetos, incluidos los literales de objetos.

Aquí hay un ejemplo rápido que puedes probar:

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

En su lugar, cree una función en la que pase el objeto.

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;
};
usuario113716
fuente
61
@patrick: dale un pan a un hombre y lo alimentarás por un día, enséñale a hornear y lo alimentarás toda la vida (o algo así, soy danés, no sé los dichos correctos en inglés ;)
Martin Jespersen
13
Lo estás haciendo mal ...! Predicado (obj [clave]) debería ser predicado (obj [clave])
pyrotechnick
66
@pyrotechnick: No. Primero, el punto principal de la respuesta es no extender Object.prototype, sino simplemente colocar la función Object. Segundo, este es el código del OP . Claramente, la intención de OP es .filter()ser tal que filtre los resultados positivos. En otras palabras, es un filtro negativo, donde un valor de retorno positivo significa que está excluido del resultado. Si observa el ejemplo jsFiddle, está filtrando las propiedades existentes que son undefined.
user113716
44
@patrick dw: No. Primero, no mencioné extender / no extender prototipos. Segundo, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "Crea una nueva matriz con todos los elementos que pasan la prueba implementada por la función proporcionada". Implementar exactamente lo contrario en un global parece bastante tonto, ¿no?
pyrotechnick
8
@pyrotechnick: Así es, no mencionaste los prototipos de extensión / no extensión, y ese es mi punto. Dijiste que lo estaba haciendo mal, pero lo único que estoy haciendo es decirle a OP que no se extienda Object.prototype. De la pregunta: "Esto funciona ..., pero cuando lo agrego a mi sitio ..., obtengo errores de JavaScript" Si OP decide implementar un .filter()con el comportamiento opuesto al de eso Array.prototpe.filter, eso depende de él / ella. Deje un comentario debajo de la pregunta si desea notificar a OP que el código está mal, pero no me diga que lo estoy haciendo mal cuando no es mi código.
user113716
284

En primer lugar, se considera una mala práctica extenderObject.prototype . En su lugar, proporcionar a su función como función de utilidad en Object, al igual que ya existen Object.keys, Object.assign, Object.is, ... etc.

Proporciono aquí varias soluciones:

  1. Usando reduceyObject.keys
  2. Como (1), en combinación con Object.assign
  3. Uso mapy sintaxis extendida en lugar dereduce
  4. Usando Object.entriesyObject.fromEntries

1. Usando reduceyObject.keys

Con reducey Object.keyspara implementar el filtro deseado (usando la sintaxis de flecha 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);

Tenga en cuenta que en el código anterior predicatedebe ser una condición de inclusión (contrario a la condición de exclusión que utilizó el OP), de modo que esté en línea con el Array.prototype.filterfuncionamiento.

2. Como (1), en combinación con Object.assign

En la solución anterior, el operador de coma se usa en la reduceparte para devolver el resobjeto mutado . Por supuesto, esto podría escribirse como dos declaraciones en lugar de una sola expresión, pero esta última es más concisa. Para hacerlo sin el operador de coma, puede usar Object.assignen su lugar, lo que devuelve el objeto mutado:

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. Uso mapy sintaxis extendida en lugar dereduce

Aquí movemos la Object.assignllamada fuera del ciclo, por lo que solo se realiza una vez, y le pasamos las teclas individuales como argumentos separados (usando la sintaxis de propagación ):

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. Usando Object.entriesyObject.fromEntries

A medida que la solución traduce el objeto a una matriz intermedia y luego la convierte de nuevo en un objeto plano, sería útil utilizar Object.entries(ES2017) y lo contrario (es decir, crear un objeto a partir de una matriz de pares clave / valor ) con Object.fromEntries( ES2019).

Conduce a este método "one-liner" en 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 función de predicado obtiene un par clave / valor como argumento aquí, que es un poco diferente, pero permite más posibilidades en la lógica de la función de predicado.

trincot
fuente
¿Podría ser una consulta más compleja? Por ejemplo:x => x.Expression.Filters.Filter
IamStalker
2
@IamStalker, ¿lo intentaste? No importa, siempre que proporcione una función válida en el segundo argumento. NB: No tengo idea de lo que .Filterestá al final, pero si es una función, debe llamarlo ( x => x.Expression.Filters.Filter())
trincot
1
Las características más nuevas pueden generar menos código, pero también reducen el rendimiento . El código compatible con Ed 3 se ejecuta más del doble de rápido en la mayoría de los navegadores.
RobG
1
Versión de TypeScript de la última variante: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
Oliver Joseph Ash
1
¡Gran trabajo! Gracias por estas excelentes explicaciones y ejemplos.
Slavik Meltser
21

Si está dispuesto a usar guiones bajos o lodash , puede usar pick(o su opuesto omit).

Ejemplos de documentos de subrayado:

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

O con una devolución de llamada (para lodash , use pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}
Bogdan D
fuente
1
lodash es una mala solución porque el filtrado de objetos vacíos también eliminará números.
mibbit
Acabo de usar lodash para esto y es una gran solución. Gracias @Bogdan D!
Ira Herman
@mibbit ¿puedes ser más específico? Creo que es solo cuestión de implementar la devolución de llamada correctamente
Bogdan D
11

Enfoque ES6 ...

Imagina que tienes este objeto a continuación:

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

Crea una función:

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

Y llámalo:

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

y va a volver :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}
Alireza
fuente
1
¡Se ve bien! Pero, ¿por qué devuelve solo el otro objeto (y no el que tiene el nombre / valor de filtro de "Alireza")?
Pille
1
@Pille, el OP solicitó que fuera así (tenga en cuenta el filtro negativo con !predicatesu propio código).
Trincot
7

Como Patrick ya dijo, esta es una mala idea, ya que casi con seguridad romperá cualquier código de terceros que pueda desear usar.

Todas las bibliotecas, como jquery o prototype, se romperán si extiende Object.prototype, la razón es que la iteración perezosa sobre los objetos (sin hasOwnPropertyverificaciones) se romperá, ya que las funciones que agregue serán parte de la iteración.

Martin Jespersen
fuente
votó por explicar el "por qué" de que esta sea una mala idea de manera clara y concisa.
Michael Liquori
5

Qué tal si:

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

O...

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

Dado

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

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

luego :

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

Abdennour TOUMI
fuente
3
Esto no utiliza una función de predicado dada como lo requiere la pregunta.
Trincot
4

He creado un Object.filter() que no solo se filtra por una función, sino que también acepta una serie de claves para incluir. El tercer parámetro opcional le permitirá invertir el filtro.

Dado:

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

Formación:

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

Función:

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

Código

Descargo de responsabilidad : elegí usar Ext JS core por brevedad. No sentí que fuera necesario escribir verificadores de tipos para los tipos de objetos ya que no era parte de la pregunta.

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

Mr. Polywhirl
fuente
Por favor revise mi respuesta y esencia de
Z. Khullah
1
@trincot Gracias, actualicé la respuesta para devolver una copia del objeto, en lugar de una devolución en el lugar.
Sr. Polywhirl
2

Mi solución obstinada:

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
}

Casos de prueba:

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

Z. Khullah
fuente
2

Solución en Vanilla JS del año 2020.


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

Puede filtrar romNumbersobjetos por clave:

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

O filtrar romNumbersobjeto por valor:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 
Qui-Gon Jinn
fuente
1

Si desea mutar el mismo objeto en lugar de crear uno nuevo.

El siguiente ejemplo eliminará todos los 0 o valores vacíos:

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);
yairniz
fuente
0

Como todos decían, no te metas con el prototipo. En cambio, simplemente escriba una función para hacerlo. Aquí está mi versión con 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;
};
saran3h
fuente
0

Lo uso cuando lo necesito:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Анна
fuente
-1

En estos casos, uso el jquery $ .map, que puede manejar objetos. Como se menciona en otras respuestas, no es una buena práctica cambiar los prototipos nativos ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

A continuación se muestra un ejemplo de filtrado simplemente marcando alguna propiedad de su objeto. Devuelve el propio objeto si su condición es verdadera o devuelve undefinedsi no. La undefinedpropiedad hará que ese registro desaparezca de su lista de objetos;

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});
Marcel Kohls
fuente
1
$.mappuede tomar un objeto, pero devuelve una matriz, por lo que se pierden los nombres de propiedad originales. El OP necesita un objeto plano filtrado.
Trincot