Filtrar propriedades do objeto por chave no ES6

266

Digamos que eu tenho um objeto:

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

Eu quero criar outro objeto filtrando o objeto acima para que eu tenha algo parecido.

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

Estou procurando uma maneira limpa de fazer isso usando o Es6, para que os operadores espalhados estejam disponíveis para mim.

29er
fonte
ES6 não tem operadores objeto propagação, e você não precisa deles aqui de qualquer maneira
Bergi
1
Possível duplicata do JavaScript: filter () para Objetos
Jonathan H
@ DanDascalescu Mas essa resposta fornece uma maneira do ES6 de realizar o que o OP pede, não é?
Jonathan H
E se eu quisesse filtrar por uma chave / valor?
jmchauv

Respostas:

503

Se você tiver uma lista de valores permitidos, poderá mantê-los facilmente em um 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);

Isso usa:

  1. Object.keyspara listar todas as propriedades raw(os dados originais) e, em seguida,
  2. Array.prototype.filter para selecionar as chaves presentes na lista de permissões, usando
    1. Array.prototype.includes para garantir que eles estejam presentes
  3. Array.prototype.reduce para criar um novo objeto apenas com as propriedades permitidas.

Isso fará uma cópia superficial com as propriedades permitidas (mas não copiará as próprias propriedades).

Você também pode usar o operador de propagação de objetos para criar uma série de objetos sem alterá-los (obrigado a rjerue por mencionar isso ):

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 fins de trivialidades, se você quiser remover os campos indesejados dos dados originais (o que eu não recomendaria, pois envolve algumas mutações feias), você pode inverter a includesverificação da seguinte forma:

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

Estou incluindo este exemplo para mostrar uma solução baseada em mutação, mas não sugiro usá-la.

ssube
fonte
1
Obrigado que funcionou muito bem. Eu também encontrei uma abordagem usando a sintaxe de desconstrução. IE: const {item1, item3} = constante const newObject = {item1, item3}
29er
2
A desconstrução funcionará (muito bem), mas é puramente em tempo de compilação. Você não pode torná-lo uma lista dinâmica de propriedades ou fornecer regras complexas (um loop pode ter retornos de chamada de validação anexados, por exemplo).
ssube
3
Eu não posso votar isso o suficiente! Bem feito para usar filtro e reduzir e não construir outro objeto a partir de um loop for. E incrível que você tenha separado explicitamente as versões imutável e mutável. +1
Sukima 03/08
3
Aviso rápido: Array.prototype.includesnão faz parte do ES6. É introduzido no ECMAScript 2016 (ES7).
Vineet
3
Se você deseja fazer a redução de uma maneira imutável, também pode substituir o conteúdo da função por return {... obj, [key]: raw [key]} #
307 de rjerue
93

Se você concorda com o uso da sintaxe ES6, acho que a maneira mais limpa de fazer isso, conforme observado aqui e aqui, é:

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

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

Agora, newDatacontém:

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

Ou, se você tiver a chave armazenada como uma sequência:

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

No último caso, [key]é convertido em, item2mas como você está usando uma constatribuição, é necessário especificar um nome para a atribuição. _representa um valor descartável.

De forma geral:

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

Isso não apenas reduz sua operação a uma linha, mas também não exige que você saiba quais são as outras chaves (aquelas que você deseja preservar).

Ryan H.
fonte
5
" _representa um valor descartável" de onde isso vem? Primeira vez que eu vê-lo
yhabib
5
Eu acredito que esta é uma convenção adotada pela comunidade JS. An _é simplesmente um nome de variável válido que pode ser usado em JS, mas como é praticamente sem nome, ele realmente não deve ser usado dessa maneira se você deseja transmitir a intenção. Portanto, foi adotado como uma maneira de denotar uma variável com a qual você não se importa. Aqui está uma discussão mais aprofundada sobre isso: stackoverflow.com/questions/11406823/...
Ryan H.
2
Isso é muito mais organizado do que a resposta aceita, evita a sobrecarga de criar uma nova matriz com Object.keys () e a sobrecarga de iterar a matriz com filtere reduce.
Ericsoco
3
@yhabib a _não importa, é apenas um nome de variável, você pode renomeá-lo para o que quiser
Vic
1
@ Gerrat Eu não acho que uma solução generalizada seria uma linha trivial. Para isso, eu usaria a omitfunção do lodash : lodash.com/docs/4.17.10#omit ou uma das outras soluções fornecidas aqui.
Ryan H.
42

A maneira mais limpa de encontrar é usando o 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
fonte
3
É importante ressaltar que, para isso, uma dependência extra do projeto deve ser baixada no tempo de execução.
S. Esteves
1
_.omit (obj, ["item2"]) - lodash.omit é o oposto de lodash.pick
Alexander Solovyev
29

Nada que não tenha sido dito antes, mas para combinar algumas respostas a uma resposta geral do 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);

Elmo de Clemens
fonte
1
esta é uma solução muito mais simples e com melhor desempenho do que outras;)
pomeh 16/01
26

Apenas mais uma solução em uma linha do Modern JS sem bibliotecas externas .

Eu estava jogando com o recurso " 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
fonte
2
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);Problema de sintaxe
Awol
1
Há algumas coisas condensadas aqui: a primeira coisa é a declaração da função. é uma função de seta (pega um objeto e retorna um objeto). É a mesma coisa que function (obj) {return obj;}. a segunda coisa é que o ES6 permite o futuro de desestruturação. Na minha declaração de função, eu desestruturo meu objeto {item1, item3}. E a última coisa é que eu invoco minha função. Você pode usar a função de auto-chamada para gerenciar seu escopo, por exemplo. Mas aqui era apenas para condensar o código. Espero que esteja claro. Se eu sentir falta de algo, fique à vontade para adicionar mais.
Novy
5
esta é a abordagem moderna preferida imho
mattdlockyer 27/07/19
20

Agora você pode torná-lo mais curto e simples usando o método Object.fromEntries (verifique o suporte ao 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)
   )
);

leia mais sobre: Object.fromEntries

dudi harush
fonte
1
Esta é agora a melhor maneira que penso. Muito mais intuitivo do que reduzir.
Damon
10

Você pode adicionar um genérico ofilter(implementado com genérico oreduce) para filtrar facilmente os objetos da mesma maneira que as matrizes -

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 vê-lo trabalhando aqui -

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 os resultados em seu próprio navegador abaixo -

Essas duas funções podem ser implementadas de várias maneiras. Eu escolhi anexar ao Array.prototype.reduceinterior, oreducemas você poderia facilmente escrever tudo do zero

Obrigado
fonte
Gosto da sua solução, mas não sei o quanto mais clara / eficiente ela é comparada a esta .
Jonathan H
3
Aqui está uma referência que mostra que sua solução é a mais rápida.
Jonathan H
No oreduce, o primeiro accé sombreado em .reduce(acc, k), e em ofiltero oé sombreado - oreduceé chamado com uma variável chamada o, bem como - qual é qual?
precisa saber é o seguinte
na ofiltervariável oé sombreada, mas sempre aponta para a mesma entrada var; é por isso que está sombreado aqui, porque é o mesmo - o acumulator ( acc) também é sombreado porque mostra mais claramente como os dados se movem através do lambda; accnem sempre é a mesma ligação , mas sempre representa o estado persistente atual do resultado computacional que desejamos retornar
Obrigado,
Enquanto o código funciona bem e o método é muito bom, eu me pergunto o que ajuda a escrever código assim para alguém que obviamente precisa de ajuda com javascript. Sou a favor da brevidade, mas isso é quase tão compacto quanto um código minimizado.
Paul G Mihai
7

Foi assim que fiz recentemente:

const dummyObj = Object.assign({}, obj);
delete dummyObj[key];
const target = Object.assign({}, {...dummyObj});
Rajat Saxena
fonte
Hum. Parece que você está misturando sintaxe antiga e nova. Object.assign == ...Você poderia escrever const dummyObj = { ...obj }e const target = { ...dummyObj }. Além disso, o último não é de todo necessário, pois você poderá trabalhar diretamente com o dummyObj posteriormente.
Andy
7

ok, que tal esse 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
fonte
2
Solução mais surpreendente de todas as respostas. +1
Gergő Horváth
e se você souber apenas as chaves que deseja remover ... não as chaves que deseja manter?
Jason
6

As respostas aqui são definitivamente adequadas, mas são um pouco lentas, porque exigem um loop na lista de permissões para todas as propriedades do objeto. A solução abaixo é muito mais rápida para grandes conjuntos de dados, porque apenas percorre a lista de permissões uma 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
fonte
5

Pegando carona na resposta do ssube .

Aqui está uma versão reutilizável.

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

Para chamá-lo, 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);

O lado bom das funções de seta do ES6 é que você não precisa passar allowedcomo parâmetro.

Evan Plaice
fonte
4

Você pode fazer algo assim:

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

Isso funciona, mas não é muito claro, além da withdeclaração não ser recomendada ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with ).

Bouni
fonte
4

Uma solução mais simples sem o uso filterpode ser alcançada com, em Object.entries()vez 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
fonte
3

Existem muitas maneiras de conseguir isso. A resposta aceita usa uma abordagem Keys-Filter-Reduce, que não é a que apresenta melhor desempenho.

Em vez disso, usar um for...inloop para percorrer as chaves de um objeto ou percorrer as chaves permitidas e depois compor um novo objeto é ~ 50% mais eficiente 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

uma. Consulte jsPerf para obter os benchmarks de um caso de uso simples. Os resultados serão diferentes com base nos navegadores.

d4nyll
fonte
3

Você pode remover uma chave específica do seu 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
fonte
3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))
Konrad Albrecht
fonte
2
Existem outras respostas que fornecem a pergunta do OP e foram publicadas há algum tempo. Ao postar uma resposta, adicione uma nova solução ou uma explicação substancialmente melhor, especialmente ao responder a perguntas mais antigas.
help-info.de
2

Maneira simples! Para fazer isso.

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
fonte
Na verdade, você não está filtrando nada aqui, apenas destruindo os dados fornecidos. Mas o que acontece quando os dados mudam?
Idris Dopico Peña
2

Outra solução usando o Array.reducemétodo "novo" :

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

Demonstração neste violino ...


Mas eu gosto da solução nesta resposta aqui que está usando e :Object.fromEntries Array.filterArray.includes

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

Demonstração neste violino ...

Wilt
fonte
Wilt, sua segunda abordagem é uma duplicata exata desta resposta fornecida no ano passado: stackoverflow.com/a/56081419/5522000
Art Schmidt
@ArtSchmidt Obrigado pelo seu comentário: Perdeu aquele na longa lista. Vou me referir a essa resposta.
Wilt
1

OK, e quanto a isso:

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

e chame assim:

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

Essa função filtrará um objeto com base em uma lista de chaves, é mais eficiente que a resposta anterior, pois não precisa usar Array.filter antes de chamar reduzir. então é O (n) em oposição 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
fonte
1

Durante o loop, não retorne nada quando determinadas propriedades / chaves forem encontradas e continue com o restante:

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
fonte
1

Estou surpreso como ninguém sugeriu isso ainda. É super limpo e muito explícito sobre quais chaves você deseja manter.

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

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

Ou se você quiser um liner sujo:

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

const filteredObject = (({a,b,c})=>({a,b,c}))(unfilteredObject);
CCD Branco
fonte
Este método adicionará uma chave não presente na matriz original. Pode ser um problema ou não, isso deve ser pelo menos apontado.
ponchietto 20/04
0

Outra abordagem seria 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)

Ele inclui valores apenas das chaves disponíveis nos dados brutos, impedindo a adição de dados indesejados.

Saksham
fonte
0

Essa seria a minha solução:

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