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í.

29er
fuente
ES6 no tiene operadores objeto de cálculo, y no se necesita aquí de todos modos
Bergi
1
Posible duplicado de JavaScript: filter () para Objetos
Jonathan H
@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.

ssube
fuente
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).

Ryan H.
fuente
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)
Guy Segev
fuente
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);

Yelmo de Clemens
fuente
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);

Novy
fuente
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

dudi harush
fuente
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

Gracias
fuente
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});
Rajat Saxena
fuente
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]})));
inabramova
fuente
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)
Joey Grisafe
fuente
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.

Evan Plaice
fuente
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 ).

Bouni
fuente
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
},{})
Itai Noam
fuente
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.

d4nyll
fuente
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;
Esin ÖNER
fuente
3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))
Konrad Albrecht
fuente
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})

ghanshyam bendkoli
fuente
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 ...

Marchitar
fuente
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' }}
Alireza
fuente
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
    }
  }, {})
}
Khaled Osman
fuente
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>
    );
});
Ryan Dhungel
fuente
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);
CCD blanco
fuente
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.

Saksham
fuente
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;
}
Анна
fuente