Como obter um subconjunto das propriedades de um objeto javascript


425

Digamos que eu tenho um objeto:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

Eu quero criar um novo objeto com um subconjunto de suas propriedades.

 // pseudo code
 subset = elmo.slice('color', 'height')

 //=> { color: 'red', height: 'unknown' }

Como posso conseguir isso?


2
A biblioteca sublinhado tem um monte de funções auxiliares como este, confira: underscorejs.org/#pick
Stefan

4
Eu pensei que disse emo.sliceà primeira vista.
Bill Criswell

1
Pensando bem ... eu não vou criar um subconjunto ...
Andrew

Respostas:


675

Usando a destruição de objetos e a abreviação de propriedades

const object = { a: 5, b: 6, c: 7  };
const picked = (({ a, c }) => ({ a, c }))(object);

console.log(picked); // { a: 5, c: 7 }


Partida Philipp Kewisch:

Esta é realmente apenas uma função anônima sendo chamada instantaneamente. Tudo isso pode ser encontrado na página Destructuring Assignment no MDN. Aqui está um formulário expandido

let unwrap = ({a, c}) => ({a, c});

let unwrap2 = function({a, c}) { return { a, c }; };

let picked = unwrap({ a: 5, b: 6, c: 7 });

let picked2 = unwrap2({a: 5, b: 6, c: 7})

console.log(picked)
console.log(picked2)


37
Como você aprendeu sobre como fazer isso? Em nenhum lugar nos documentos ou artigos que eu vi (incluindo MDN) ele mostra a sintaxe da seta sendo usada na Reestruturação de Objetos. É muito bom saber disso.
Papiro

52
Esta é realmente apenas uma função anônima sendo chamada instantaneamente. Tudo isso pode ser encontrado na página Destructuring Assignment no MDN. Aqui está uma forma expandida: let unwrap = ({a, c}) => ({a, c}); let unwrap2 = function({a, c}) { return { a, c }; }; let picked = unwrap({ a: 5, b: 6, c: 7 });
Philipp Kewisch 27/01

8
existe uma maneira de fazer isso dinamicamente com o operador de spread?
precisa

4
@ TomSarduy, você pode usar o resto se quiser especificar quais objetos remover, por exemplo const { b, ...picked } = object, criaria pickedcomo { a: 5, c: 7 }. Você especificou simplesmente para remover b. Seu eslint provavelmente ficará chateado com você por declarar um var que você não está usando.
Josh de Qaribou 28/08

24
Uma desvantagem aqui é que você precisa digitar totalmente a série de nomes de atributos duas vezes. Isso pode ser um problema nos casos em que muitos atributos precisam ser escolhidos.
Gershom

144

Sugiro dar uma olhada no Lodash ; possui muitas funções excelentes de utilidade.

Por exemplo, pick()seria exatamente o que você procura:

var subset = _.pick(elmo, ['color', 'height']);

violino


2
o mesmo para underscore.js
Dan

1
Existe alguma função para excluir apenas determinados campos em vez de selecionar? então eu tenho cerca de 50 campos no meu json e quero tudo, exceto apenas 2 campos.
Shrikant Prabhu

7
Sim! você pode usar _.omit(elmo, ['voice'])para retornar tudo, excetovoice
xavdid

o que eu não gosto nessa abordagem é que você coloca os nomes dos campos entre aspas, para que sejam suscetíveis a erros de digitação, refatorações comuns como renomear uma propriedade no IDE não serão atendidas etc.
Andy

117

Se você estiver usando o ES6, há uma maneira muito concisa de fazer isso usando a desestruturação. A reestruturação permite adicionar facilmente objetos usando uma propagação, mas também permite criar objetos de subconjunto da mesma maneira.

const object = {
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}

// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};

console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};

6
isso funciona apenas para remover um campo, não para selecionar um subconjunto conhecido? potencialmente um número infinito de campos desconhecidos de remover, mas pode ser que algumas pessoas estão procurando
Alexander Mills

É verdade, mas ele pode remover vários campos conhecidos que podem ser reatribuídos para um novo objeto, para que ainda pareça relevante para esta pergunta. Adicionado à resposta para ilustrar melhor.
27418 Lauren

1
Este é essencialmente o mesmo que o que está em como a resposta de @Ivan Nosov , embora é explicado de uma forma mais compreensível aqui
icc97

81

Embora seja um pouco mais detalhado, você pode realizar o que todo mundo recomendou o sublinhado / lodash por 2 anos atrás, usando Array.prototype.reduce .

var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});

Essa abordagem resolve do outro lado: em vez de pegar um objeto e passar nomes de propriedades para extrair, pegar uma matriz de nomes de propriedades e reduzi-los a um novo objeto.

Embora seja mais detalhado no caso mais simples, um retorno de chamada aqui é bastante útil, pois você pode atender facilmente a alguns requisitos comuns, por exemplo, altere a propriedade 'color' para 'color' no novo objeto, aplainar matrizes etc. - as coisas que você precisa fazer ao receber um objeto de um serviço / biblioteca e criar um novo objeto necessário em outro lugar. Embora o sublinhado / lodash sejam excelentes e bem implementadas, esta é a minha abordagem preferida para menos confiança do fornecedor e uma abordagem mais simples e consistente quando minha lógica de criação de subconjuntos fica mais complexa.

edit: es7 versão do mesmo:

const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});

editar: Um bom exemplo para curry também! Faça com que uma função 'pick' retorne outra função.

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});

O acima é bem parecido com o outro método, exceto que permite criar um 'selecionador' em tempo real. por exemplo

pick('color', 'height')(elmo);

O que é especialmente interessante sobre essa abordagem é que você pode facilmente passar as 'escolhas' escolhidas para qualquer coisa que desempenhe uma função, por exemplo Array#map:

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]

3
ES6 torna possível para que isso seja ainda mais limpa através de funções de seta, e o retorno de Object.assign (desde a atribuição de uma propriedade de objeto retorna o valor da propriedade, mas Object.assign retorna o objeto.)
Josh partir Qaribou

Outra observação importante: você raramente precisará mais fazer isso, uma vez que normalmente apenas desestrutura a atribuição ou os argumentos. por exemplo function showToy({ color, height }) {, colocaria apenas o que você precisa em escopo. A reduceabordagem faz sentido principalmente quando você está simplificando objetos para serialização.
Josh de Qaribou 08/04

6
Essa versão do ES6 tem menos desempenho, porque faz uma cópia de todas as propriedades a cada iteração. Torna uma operação O (n) em O (n ^ 2). Um ES6 equivalente de seu primeiro bloco de código seriaconst pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
4castle

@ 4castle sim boa chamada - não faz sentido iterar tanto. Gosto da sintaxe da vírgula - melhor do que um monte de retornos.
Josh de Qaribou

1
@ShevchenkoViktor Na verdade, eu usei essa abordagem na minha versão original do es6, mas a mudei após o comentário do @castle. Eu acho que a propagação é mais clara, mas é uma grande diferença para objetos maiores no código que podem estar facilmente em um gargalo (por exemplo, atrasar a renderização dos dados retornados fetch), então eu recomendo adicionar um comentário explicando o uso do operador de vírgula.
Josh de Qaribou

45

ES6 desestruturação

A sintaxe da reestruturação permite desestruturar e recombinar um objeto, com parâmetros ou variáveis ​​de função.

A limitação é que uma lista de chaves é predefinida, elas não podem ser listadas como seqüências de caracteres, conforme mencionado na pergunta. A reestruturação se torna mais complicada se uma chave não é alfanumérica, por exemplo foo_bar.

A desvantagem é que isso requer duplicar uma lista de chaves, resultando em código detalhado, caso uma lista seja longa. Como a desestruturação duplica a sintaxe literal do objeto nesse caso, uma lista pode ser copiada e colada como está.

A vantagem é que é uma solução de alto desempenho que é natural para o ES6.

IIFE

let subset = (({ foo, bar }) => ({ foo, bar }))(obj); // dupe ({ foo, bar })

Variáveis ​​temporárias

let { foo, bar } = obj;
let subset = { foo, bar }; // dupe { foo, bar }

Uma lista de strings

A lista arbitrária de chaves selecionadas consiste em seqüências de caracteres, conforme a pergunta. Isso permite não predefini-los e usar variáveis ​​que contêm nomes de chave, comopick(obj, 'foo', someKey, ...moreKeys) .

Um one-liner fica mais curto a cada edição JS.

ES5

var subset = Object.keys(obj)
.filter(function (key) { 
  return ['foo', 'bar'].indexOf(key) >= 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});

ES6

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => Object.assign(obj2, { [key]: obj[key] }), {});

Ou com o operador de vírgula:

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

ES2019

O ECMAScript 2017 possui Object.entriese Array.prototype.includes, o ECMAScript 2019 possui Object.fromEntries, eles podem ser polyfilled quando necessário e facilitar a tarefa:

let subset = Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['foo', 'bar'].includes(key))
)

Um one-liner pode ser reescrito como função auxiliar semelhante ao Lodashpick ou omitonde a lista de chaves é passada através de argumentos:

let pick = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => keys.includes(key))
);

let subset = pick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1 }

Uma observação sobre falta de chaves

A principal diferença entre a desestruturação e a função convencional do tipo Lodash pické que a desestruturação inclui chaves selecionadas inexistentes com undefinedvalor em um subconjunto:

(({ foo, bar }) => ({ foo, bar }))({ foo: 1 }) // { foo: 1, bar: undefined }

Esse comportamento pode ou não ser desejável. Não pode ser alterado para desestruturar a sintaxe.

Enquanto pickpode ser alterado para incluir chaves ausentes, iterando uma lista de chaves selecionadas:

let inclusivePick = (obj, ...keys) => Object.fromEntries(
  keys.map(key => [key, obj[key]])
);

let subset = inclusivePick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1, bar: undefined }

11
Que pena que essa resposta seja tão recente e, portanto, não receba a exposição que merece. Na IMO, deve ser a resposta aceita por completude, simplicidade, versatilidade e praticidade. Manterei a versão ES6 na minha biblioteca de trechos mais úteis.
21819 VanAlbert

ES5 - "Referência não
detectadoErro

@NikhilVartak Espera-se que a objvariável seja uma variável que armazene um objeto. Se o nome da sua variável for diferente, use-o em vez de obj.
Estus Flask

Foi mal! Eu esqueci Object.keys(obj). Sonolento.
Nikhil Vartak

34

Não há nada como isso embutido na biblioteca principal, mas você pode usar a destruição de objetos para fazer isso ...

const {color, height} = sourceObject;
const newObject = {color, height};

Você também pode escrever uma função utilitária fazê-lo ...

const cloneAndPluck = function(sourceObject, keys) {
    const newObject = {};
    keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
    return newObject;
};

const subset = cloneAndPluck(elmo, ["color", "height"]);

Bibliotecas como o Lodash também possuem _.pick().


1
ótimo, eu apenas tive que alterar o forEach para: keys.forEach (key => {newObject [key] = sourceObject [key];}) ;. Atualize o comentário, se isso fizer sentido.
kandan

34

Estou adicionando esta resposta porque nenhuma resposta foi usada Comma operator.

É muito fácil com destructuring assignmente ,operador

const object = { a: 5, b: 6, c: 7  };
const picked = ({a,c} = object, {a,c})

console.log(picked);

Com um certo aprimoramento para a atribuição de propriedades dinâmicas, pode ser assim:


2
meu mau, eu não sei o que eu fiz antes que não fez trabalho, mas parece funcionar
ekkis

1
Essa é a melhor expressão, quando ele chegou à desestruturação
Mohamed Allal

1
Esta solução é inteligente, mas não funciona no modo estrito (ou seja, 'use strict'). Eu recebo um ReferenceError: a is not defined.
Kimbaudi 18/07/19

8
Observe que essa abordagem polui o escopo atual com duas variáveis ae c- tome cuidado para não sobrescrever aleatoriamente vars locais ou globais, dependendo do contexto. (A resposta aceita evita esse problema usando duas variáveis locais em uma função inline, que cai fora do escopo após a execução imediata.)
mindplay.dk

2
A poluição do namespace torna isso completamente impraticável. É extremamente comum já ter variáveis ​​no escopo que correspondem às propriedades do objeto; é por isso que existe a abreviação do prop + a desestruturação. Muito provavelmente você terá altura ou cor já definidas, como no exemplo original.
Josh de Qaribou

23

Mais uma solução:

var subset = {
   color: elmo.color,
   height: elmo.height 
}

Isso parece muito mais legível para mim do que praticamente qualquer resposta até agora, mas talvez seja apenas eu!


9
Prefiro ser produtivo do que um código sofisticado, mas confuso, e na engenharia de software da vida real essa é de longe a solução mais legível e sustentável.
Janos

1
Sim, no entanto, para mim, uma vantagem do uso de notação de desestruturação e abreviação é que é menos propenso a erros. Se eu tivesse um centavo por cada vez que copiei e colei erroneamente o código para terminar subset = {color: elmo.color, height: elmo.color}, eu teria pelo menos um ... bem, um centavo talvez.
JHH 10/01

Eu não chamaria a taquigrafia destruidora de menos propensa a erros, pois não é SECA
gman 7/04

Eu teria que concordar. Sem poluir o contexto com variáveis ​​indesejadas, essa é de longe a solução mais legível. O resto parece muito confuso. Prefiro entender meu código no segundo em que olho para ele.
Andrew

11

Você também pode usar o Lodash .

var subset = _.pick(elmo ,'color', 'height');

Complementando, digamos que você tenha uma variedade de "elmo" s:

elmos = [{ 
      color: 'red',
      annoying: true,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'blue',
      annoying: true,
      height: 'known',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'yellow',
      annoying: false,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    }
];

Se você deseja o mesmo comportamento, usando o lodash, basta:

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });

10

A desestruturação em variáveis ​​nomeadas dinamicamente é impossível no JavaScript, conforme discutido nesta pergunta .

Para definir teclas dinamicamente , você pode usar a função reduzir sem alterar o objeto da seguinte maneira:

const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)

Observe que você está criando um novo objeto em cada iteração, em vez de atualizar um único clone. - mpen

abaixo está uma versão usando o botão reduzir com clone único (atualizando o valor inicial passado para reduzir).

const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
  acc[curr] = obj[curr]
  return acc
}, {})

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)


1
Impressionante! Por um momento, fiquei desconcertado com a importância dos colchetes em [c]:. Eu estou supondo que de alguma forma está causando c ser visto como um valor em vez do nome da propriedade. Enfim, muito legal. 1
John Fairbanks

Obrigado companheiro! Esse uso é uma coisa que eu amo no JavaScript, que habilita funções genéricas sem usar o eval. Simplesmente, o que ele faz é permitir que você defina uma chave de regra para uma variável em tempo de execução. se você definir var key = 'someKey', poderá usá-lo como {[key]: 'value'}, que fornece {someKey: 'value'}. Realmente legal.
Muhammet Enginar 15/03/18

1
Observe que você está criando um novo objeto em cada iteração, em vez de atualizar um único clone.
mpen 16/05/19

1
@mpen boa localização. Eu adicionei uma versão que modifica o clone único, como você sugeriu também espalhar args em vez de passar uma matriz de chaves.
Muhammet Enginar

7

Solução TypeScript:

export function pick<T extends object, U extends keyof T>(obj: T, paths: Array<U>) {
    return paths.reduce((o, k) => {
        o[k] = obj[k];
        return o;
    }, Object.create(null));
}

As informações de digitação ainda permitem o preenchimento automático:

Crédito para DefinitelyTyped para U extends keyof Ttruque!


Não está trabalhando para mim.
Nuthinking

@Nuthinking Você está usando o VS Code?
mpen

Sim, eu uso o VS Code.
Nuthinking

@Nuthinking E você coloca isso em um arquivo .ts? Isso deve funcionar. Eu apenas tentei novamente
mpen


4

Solução dinâmica

['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})


3

Apenas outra maneira ...

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

var subset = [elmo].map(x => ({
  color: x.color,
  height: x.height
}))[0]

Você pode usar esta função com uma matriz de objetos =)


2

E se:

function sliceObj(obj) {
  var o = {}
    , keys = [].slice.call(arguments, 1);
  for (var i=0; i<keys.length; i++) {
    if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
  }
  return o;
}

var subset = sliceObj(elmo, 'color', 'height');

Isso falharia se o valor da propriedade fosse false(ou falso). jsfiddle.net/nfAs8
Alxandr

Por isso mudei para keys[i] in obj.
Elclanrs 22/07/2013

2

Isso funciona para mim no console do Chrome. Algum problema com isso?

var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}

4
Isso parece legal, mas cria duas variáveis ​​desnecessárias, cor e altura.
user424174

Não entendo o seu comentário. O requisito do OP era criar um objeto com esses dois elementos #
MSi

@MSi Isso não cria apenas um objeto, mas também cria duas variáveis.
Design by Adrian

1

Atribuição de reestruturação com propriedades dinâmicas

Esta solução não se aplica apenas ao seu exemplo específico, mas é mais geralmente aplicável:

const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});

const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});

// const subset4...etc.


const o = {a:1, b:2, c:3, d:4, e:5};


const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");


console.log(
  pickBD(o), // {b:2, d:4}
  pickACE(o) // {a:1, c:3, e:5}
);

Você pode definir facilmente subset4etc. para levar mais propriedades em consideração.


1

Bom e velho Array.prototype.reduce:

const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};

const r = Object.keys(selectable).reduce((a, b) => {
  return (a[b] = v[b]), a;
}, {});

console.log(r);

esta resposta usa o operador de vírgula mágico, também: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

se você quiser ser realmente chique, isso é mais compacto:

const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});

Juntando tudo em uma função reutilizável:

const getSelectable = function (selectable, original) {
  return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};

const r = getSelectable(selectable, v);
console.log(r);

1
  1. converter argumentos em array

  2. use Array.forEach()para escolher a propriedade

    Object.prototype.pick = function(...args) {
       var obj = {};
       args.forEach(k => obj[k] = this[k])
       return obj
    }
    var a = {0:"a",1:"b",2:"c"}
    var b = a.pick('1','2')  //output will be {1: "b", 2: "c"}

4
Estender o protótipo de tipos nativos é considerado uma prática ruim, embora funcione. Não faça isso se estiver escrevendo uma biblioteca.
Emile Bergeron

0
function splice()
{
    var ret = new Object();

    for(i = 1; i < arguments.length; i++)
        ret[arguments[i]] = arguments[0][arguments[i]];

    return ret;
}

var answer = splice(elmo, "color", "height");

0

Tentar

const elmo={color:"red",annoying:!0,height:"unknown",meta:{one:"1",two:"2"}};

const {color, height} = elmo; newObject = ({color, height});

console.log(newObject); //{ color: 'red', height: 'unknown' }

0

Adicionando meus 2 centavos à resposta de Ivan Nosov :

No meu caso, eu precisava que muitas chaves fossem 'fatiadas' do objeto, para que se tornasse feio muito rápido e não uma solução muito dinâmica:

const object = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const picked = (({ a, aa, aaa, ab, c, cc, ccc, cd }) => ({ a, aa, aaa, ab, c, cc, ccc, cd }))(object);

console.log(picked);

Então, aqui está uma solução dinâmica usando eval:

const slice = (k, o) => eval(`(${k} => ${k})(o)`);


const object    = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const sliceKeys = '({ a, aa, aaa, ab, c, cc, ccc, cd })';

console.log( slice(sliceKeys, object) );

-2

Nota: embora a pergunta original feita fosse javascript, isso pode ser feito com o jQuery pela solução abaixo

você pode estender o jquery se quiser, aqui está o código de exemplo para uma fatia:

jQuery.extend({
  sliceMe: function(obj, str) {
      var returnJsonObj = null;
    $.each( obj, function(name, value){
        alert("name: "+name+", value: "+value);
        if(name==str){
            returnJsonObj = JSON.stringify("{"+name+":"+value+"}");
        }

    });
      return returnJsonObj;
  }
});

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};


var temp = $.sliceMe(elmo,"color");
alert(JSON.stringify(temp));

aqui está o violino para o mesmo: http://jsfiddle.net/w633z/


16
O que é jquery?
Christian Schlensker

1
@ChristianSchlensker é javascript
Kevin B
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.