Filtrar propiedades de objeto por clave en ES6


266

Digamos que tengo un objeto:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

Quiero crear otro objeto filtrando el objeto de arriba, así que tengo algo así.

 {
    item1: { key: 'sdfd', value:'sdfd' },
    item3: { key: 'sdfd', value:'sdfd' }
 }

Estoy buscando una manera limpia de lograr esto usando Es6, por lo que los operadores de propagación están disponibles para mí.


ES6 no tiene operadores objeto de cálculo, y no se necesita aquí de todos modos
Bergi


@DanDascalescu Pero esta respuesta da una forma ES6 de lograr lo que pide el OP, ¿no?
Jonathan H

¿Qué pasa si quisiera filtrar por una clave / valor?
jmchauv

Respuestas:


503

Si tiene una lista de valores permitidos, puede retenerlos fácilmente en un objeto usando:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    obj[key] = raw[key];
    return obj;
  }, {});

console.log(filtered);

Esto usa:

  1. Object.keyspara enumerar todas las propiedades en raw(los datos originales), luego
  2. Array.prototype.filter para seleccionar claves que están presentes en la lista permitida, usando
    1. Array.prototype.includes para asegurarse de que estén presentes
  3. Array.prototype.reduce para construir un nuevo objeto con solo las propiedades permitidas.

Esto hará una copia superficial con las propiedades permitidas (pero no copiará las propiedades en sí).

También puede usar el operador de propagación de objetos para crear una serie de objetos sin mutarlos (gracias a rjerue por mencionar esto ):

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    return {
      ...obj,
      [key]: raw[key]
    };
  }, {});

console.log(filtered);

Para fines de trivia, si desea eliminar los campos no deseados de los datos originales (lo que no recomendaría hacer, ya que implica algunas mutaciones feas), podría invertir el includescheque de la siguiente manera:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

Object.keys(raw)
  .filter(key => !allowed.includes(key))
  .forEach(key => delete raw[key]);

console.log(raw);

Incluyo este ejemplo para mostrar una solución basada en la mutación, pero no sugiero usarla.


1
Gracias que funcionó muy bien. También encontré un enfoque usando la sintaxis de deconstrucción. IE: const {item1, item3} = raw const newObject = {item1, item3}
29er

2
La deconstrucción funcionará (bien), pero es puramente tiempo de compilación. No puede convertirlo en una lista dinámica de propiedades o proporcionar reglas complejas (un bucle puede tener devoluciones de llamada de validación adjuntas, por ejemplo).
ssube

3
¡No puedo votar esto lo suficiente! Bien hecho por usar filter and reduce y no construir otro objeto a partir de un bucle for. Y sorprendente que separaste explícitamente las versiones inmutable y mutable. +1
Sukima

3
Advertencia rápida: Array.prototype.includesno es parte de ES6. Se introdujo en ECMAScript 2016 (ES7).
Vineet

3
Si desea hacer la reducción de una manera inmutable, también puede reemplazar el contenido de la función con return {... obj, [key]: raw [key]}
rjerue

93

Si está de acuerdo con el uso de la sintaxis ES6, creo que la forma más limpia de hacerlo, como se señala aquí y aquí, es:

const data = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const { item2, ...newData } = data;

Ahora newDatacontiene:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

O, si tiene la clave almacenada como una cadena:

const key = 'item2';
const { [key]: _, ...newData } = data;

En este último caso, [key] se convierte en item2pero dado que está utilizando una constasignación, debe especificar un nombre para la asignación. _representa un valor de descarte.

Más generalmente:

const { item2, ...newData } = data; // Assign item2 to item2
const { item2: someVarName, ...newData } = data; // Assign item2 to someVarName
const { item2: _, ...newData } = data; // Assign item2 to _
const { ['item2']: _, ...newData } = data; // Convert string to key first, ...

Esto no solo reduce su operación a una sola línea, sino que tampoco requiere que sepa cuáles son las otras claves (las que desea conservar).


55
" _representa un valor de descarte" ¿de dónde viene esto? Primera vez que lo veo
yhabib

55
Creo que esta es una convención adoptada por la comunidad JS. An _es simplemente un nombre de variable válido que se puede usar en JS, pero dado que prácticamente no tiene nombre, realmente no se debe usar de esa manera si te importa transmitir intenciones. Por lo tanto, se ha adoptado como una forma de denotar una variable que no le importa. Aquí hay más discusión al respecto: stackoverflow.com/questions/11406823/…
Ryan H.

2
Esto es mucho más ordenado que la respuesta aceptada, evita la sobrecarga de crear una nueva matriz con Object.keys () y la sobrecarga de iterar la matriz con filtery reduce.
ericsoco

3
@yhabib _no importa, es solo un nombre de variable, puedes cambiarle el nombre a lo que quieras
Vic

1
@Gerrat No creo que una solución generalizada sea trivial. Para eso, usaría la omitfunción de lodash : lodash.com/docs/4.17.10#omit o una de las otras soluciones dadas aquí.
Ryan H.

42

La forma más limpia que puedes encontrar es con Lodash # pick

const _ = require('lodash');

const allowed = ['item1', 'item3'];

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

const filteredObj = _.pick(obj, allowed)

3
Es importante señalar que para ello se debe descargar una dependencia de proyecto adicional en tiempo de ejecución.
S. Esteves

1
_.omit (obj, ["item2"]) - lodash.omit es un opuesto a lodash.pick
Alexander Solovyev

29

Nada que no se haya dicho antes, pero para combinar algunas respuestas a una respuesta general de ES6:

const raw = {
  item1: { key: 'sdfd', value: 'sdfd' },
  item2: { key: 'sdfd', value: 'sdfd' },
  item3: { key: 'sdfd', value: 'sdfd' }
};

const filteredKeys = ['item1', 'item3'];

const filtered = filteredKeys
  .reduce((obj, key) => ({ ...obj, [key]: raw[key] }), {});

console.log(filtered);


1
esta es una solución mucho más simple y
eficaz

26

Solo otra solución en una línea de Modern JS sin bibliotecas externas .

Estaba jugando con la función " Destructuring ":

const raw = {
    item1: { key: 'sdfd', value: 'sdfd' },
    item2: { key: 'sdfd', value: 'sdfd' },
    item3: { key: 'sdfd', value: 'sdfd' }
  };
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);
console.log(myNewRaw);


2
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);
Problema de

1
Aquí hay un par de cosas condensadas: lo primero es la declaración de la función. Es una función de flecha (toma un objeto y devuelve un objeto). Es lo mismo que function (obj) {return obj;}. Lo segundo es que ES6 permite la futura desestructuración. En mi declaración de función, desestructura mi objeto {item1, item3}. Y lo último es que yo mismo invoco mi función. Puede utilizar la función de auto invocación para administrar su alcance, por ejemplo. Pero aquí era solo para condensar el código. Espero que quede claro. Si extraño algo, siéntase libre de agregar más.
Novy

55
este es el enfoque moderno en mi humilde opinión preferido
mattdlockyer

20

Ahora puede hacerlo más corto y simple utilizando el método Object.fromEntries (consulte el soporte del navegador):

const raw = { item1: { prop:'1' }, item2: { prop:'2' }, item3: { prop:'3' } };

const allowed = ['item1', 'item3'];

const filtered = Object.fromEntries(
   Object.entries(raw).filter(
      ([key, val])=>allowed.includes(key)
   )
);

Leer más sobre: Object.fromEntries


1
Esta es ahora la mejor forma en que pienso. Mucho más intuitivo que reducir.
Damon

10

Puede agregar un genérico ofilter(implementado con genérico oreduce) para que pueda filtrar fácilmente los objetos de la misma manera que los arreglos:

const oreduce = (f, acc, o) =>
  Object
    .entries (o)
    .reduce
      ( (acc, [ k, v ]) => f (acc, v, k, o)
      , acc
      )

const ofilter = (f, o) =>
  oreduce
    ( (acc, v, k, o)=>
        f (v, k, o)
          ? Object.assign (acc, {[k]: v})
          : acc
    , {}
    , o
    )

Podemos verlo trabajando aquí.

const data =
  { item1: { key: 'a', value: 1 }
  , item2: { key: 'b', value: 2 }
  , item3: { key: 'c', value: 3 }
  }

console.log
  ( ofilter
      ( (v, k) => k !== 'item2'
      , data
      )
      // [ { item1: { key: 'a', value: 1 } }
      // , { item3: { key: 'c', value: 3 } }
      // ]

  , ofilter
      ( x => x.value === 3
      , data
      )
      // [ { item3: { key: 'c', value: 3 } } ]
  )

Verifique los resultados en su propio navegador a continuación:

Estas dos funciones podrían implementarse de muchas maneras. Elegí unirme aArray.prototype.reduce interior, oreducepero podrías escribirlo fácilmente desde cero


Me gusta su solución, pero no sé cuánto más clara / eficiente es en comparación con esta .
Jonathan H

3
Aquí hay un punto de referencia que muestra que su solución es la más rápida.
Jonathan H

En oreduce, el primero accestá sombreado .reduce(acc, k), y en ofilterel oestá sombreado, oreducese llama con una variable llamada otambién, ¿cuál es cuál?
Jarrod Mosen

en ofilterla variable oestá sombreada pero siempre apunta a la misma entrada var; es por eso que está sombreado aquí, porque es lo mismo: el acumulador ( acc) también está sombreado porque muestra más claramente cómo se mueven los datos a través de la lambda; accno siempre es el mismo enlace , pero siempre representa el estado actual persistente del resultado computacional que deseamos devolver
Gracias,

Si bien el código funciona bien y el método es muy bueno, me pregunto qué ayuda escribir código así es para alguien que obviamente necesita ayuda con JavaScript. Estoy a favor de la brevedad, pero eso es casi tan compacto como un código minimizado.
Paul G Mihai

7

Así es como lo hice recientemente:

const dummyObj = Object.assign({}, obj);
delete dummyObj[key];
const target = Object.assign({}, {...dummyObj});

Hm. Parece que estás mezclando sintaxis antigua y nueva. Object.assign == ...Podrías escribir const dummyObj = { ...obj }y const target = { ...dummyObj }. Además, esto último no es del todo necesario, ya que podría trabajar directamente con dummyObj después.
Andy

7

ok, ¿qué tal este one-liner

    const raw = {
      item1: { key: 'sdfd', value: 'sdfd' },
      item2: { key: 'sdfd', value: 'sdfd' },
      item3: { key: 'sdfd', value: 'sdfd' }
    };

    const filteredKeys = ['item1', 'item3'];

    const filtered = Object.assign({}, ...filteredKeys.map(key=> ({[key]:raw[key]})));

2
La solución más sorprendente de todas las respuestas. +1
Gergő Horváth

¿Qué sucede si solo conoce las claves que desea eliminar ... no las claves que desea conservar?
Jason

6

Las respuestas aquí son definitivamente adecuadas, pero son un poco lentas porque requieren recorrer la lista blanca para cada propiedad del objeto. La solución a continuación es mucho más rápida para grandes conjuntos de datos porque solo recorre la lista blanca una vez:

const data = {
  allowed1: 'blah',
  allowed2: 'blah blah',
  notAllowed: 'woah',
  superSensitiveInfo: 'whooooah',
  allowed3: 'bleh'
};

const whitelist = ['allowed1', 'allowed2', 'allowed3'];

function sanitize(data, whitelist) {
    return whitelist.reduce(
      (result, key) =>
        data[key] !== undefined
          ? Object.assign(result, { [key]: data[key] })
          : result,
      {}
    );
  }

  sanitize(data, whitelist)

5

Piggybacking en la respuesta de ssube .

Aquí hay una versión reutilizable.

Object.filterByKey = function (obj, predicate) {
  return Object.keys(obj)
    .filter(key => predicate(key))
    .reduce((out, key) => {
      out[key] = obj[key];
      return out;
    }, {});
}

Para llamarlo use

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

var filtered = Object.filterByKey(raw, key => 
  return allowed.includes(key));
});

console.log(filtered);

Lo bueno de las funciones de flecha de ES6 es que no tiene que pasar allowedcomo parámetro.


4

Puedes hacer algo como esto:

const base = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const filtered = (
    source => { 
        with(source){ 
            return {item1, item3} 
        } 
    }
)(base);

// one line
const filtered = (source => { with(source){ return {item1, item3} } })(base);

Esto funciona pero no está muy claro, además de withque no se recomienda la declaración ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with ).


4

Se filterpuede lograr una solución más simple sin usar con en Object.entries()lugar deObject.keys()

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.entries(raw).reduce((acc,elm)=>{
  const [k,v] = elm
  if (allowed.includes(k)) {
    acc[k] = v 
  }
  return acc
},{})

3

Hay muchas formas de lograr esto. La respuesta aceptada utiliza un enfoque Keys-Filter-Reduce, que no es el más eficaz.

En cambio, usar un for...inbucle para recorrer las teclas de un objeto, o recorrer las teclas permitidas, y luego componer un nuevo objeto es ~ 50% más de rendimiento a .

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const keys = ['item1', 'item3'];

function keysReduce (obj, keys) {
  return keys.reduce((acc, key) => {
    if(obj[key] !== undefined) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
};

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

keysReduce(obj, keys);   // Faster if the list of allowed keys are short
forInCompose(obj, keys); // Faster if the number of object properties are low

a. Ver jsPerf para los puntos de referencia de un caso de uso simple. Los resultados diferirán según los navegadores.


3

Puedes eliminar una clave específica de tu objeto

items={
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

// Example 1
var key = "item2";
delete items[key]; 

// Example 2
delete items["item2"];

// Example 3
delete items.item2;

3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))

2
Hay otras respuestas que proporcionan la pregunta del OP, y se publicaron hace algún tiempo. Cuando publique una respuesta, asegúrese de agregar una nueva solución o una explicación sustancialmente mejor, especialmente al responder preguntas anteriores.
help-info.de

2

¡Manera simple! Para hacer esto.

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};
const{item1,item3}=myData
const result =({item1,item3})


En realidad no está filtrando nada aquí, solo está desestructurando los datos proporcionados. Pero, ¿qué sucede cuando cambian los datos?
Idris Dopico Peña

2

Otra solución usando el Array.reducemétodo "nuevo" :

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = allowed.reduce((obj, key) => { 
  obj[key] = raw[key]; 
  return obj 
}, {})

console.log(filtered);

Demostración en este violín ...


Pero me gusta la solución en esta respuesta aquí que está usando y :Object.fromEntries Array.filterArray.includes

const object = Object.fromEntries( Object.entries(raw).filter(([key, value]) => allowed.includes(key)) );

Demostración en este violín ...


Wilt, su segundo enfoque es un duplicado exacto de esta respuesta proporcionada el año pasado: stackoverflow.com/a/56081419/5522000
Art Schmidt

@ArtSchmidt Gracias por tu comentario: Perdí ese en la larga lista. Me referiré a esa respuesta en su lugar.
marchita el

1

OK, ¿qué tal esto?

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

function filteredObject(obj, filter) {
  if(!Array.isArray(filter)) {
   filter = [filter.toString()];
  }
  const newObj = {};
  for(i in obj) {
    if(!filter.includes(i)) {
      newObj[i] = obj[i];
    }
  }
  return newObj;
}

y llámalo así:

filteredObject(myData, ['item2']); //{item1: { key: 'sdfd', value:'sdfd' }, item3: { key: 'sdfd', value:'sdfd' }}

1

Esta función filtrará un objeto basado en una lista de claves, es más eficiente que la respuesta anterior ya que no tiene que usar Array.filter antes de llamar a reduce. entonces su O (n) en oposición a O (n + filtrado)

function filterObjectByKeys (object, keys) {
  return Object.keys(object).reduce((accum, key) => {
    if (keys.includes(key)) {
      return { ...accum, [key]: object[key] }
    } else {
      return accum
    }
  }, {})
}

1

Durante el ciclo, no devuelva nada cuando se encuentren ciertas propiedades / claves y continúe con el resto:

const loop = product =>
Object.keys(product).map(key => {
    if (key === "_id" || key === "__v") {
        return; 
    }
    return (
        <ul className="list-group">
            <li>
                {product[key]}
                <span>
                    {key}
                </span>
            </li>
        </ul>
    );
});

1

Me sorprende que nadie haya sugerido esto todavía. Es súper limpio y muy explícito sobre las teclas que desea mantener.

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filterObject = ({a,b,c}) => ({a,b,c})
const filteredObject = filterObject(unfilteredObject)

O si quieres un revestimiento sucio:

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filteredObject = (({a,b,c})=>({a,b,c}))(unfilteredObject);

Este método agregará una clave no presente en la matriz original. Puede ser un problema o no, esto debería al menos señalarse.
ponchietto

0

Otro enfoque sería usar Array.prototype.forEach()como

const raw = {
  item1: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item2: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item3: {
    key: 'sdfd',
    value: 'sdfd'
  }
};

const allowed = ['item1', 'item3', 'lll'];

var finalObj = {};
allowed.forEach(allowedVal => {
  if (raw[allowedVal])
    finalObj[allowedVal] = raw[allowedVal]
})
console.log(finalObj)

Incluye valores de solo aquellas claves que están disponibles en los datos sin procesar y, por lo tanto, evita agregar datos no deseados.


0

Esa sería mi solución:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.