Cómo obtener un subconjunto de propiedades de un objeto javascript


425

Digamos que tengo un objeto:

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

Quiero hacer un nuevo objeto con un subconjunto de sus propiedades.

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

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

¿Cómo puedo lograr esto?


2
La biblioteca Underscore tiene muchas funciones auxiliares como esta, échale un vistazo: underscorejs.org/#pick
Stefan

44
Pensé que dicho emo.slicea primera vista.
Bill Criswell

1
Pensándolo bien ... no crearé un subconjunto ...
Andrew

Respuestas:


675

Uso de la desestructuración de objetos y la taquigrafía de propiedades

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

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


De Philipp Kewisch:

Esta es realmente una función anónima que se llama instantáneamente. Todo esto se puede encontrar en la página de Destructuring Assignment en MDN. Aquí hay una 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 });

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

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


37
¿Cómo se enteró de cómo hacer esto? En ninguna parte de los documentos o artículos que he visto (incluido MDN) muestra la sintaxis de flecha que se utiliza en la Desestructuración de objetos. Es muy bueno saberlo.
papiro

52
Esta es realmente una función anónima que se llama instantáneamente. Todo esto se puede encontrar en la página de Destructuring Assignment en MDN. Aquí hay una forma ampliada: 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

8
¿Hay alguna manera de hacerlo dinámicamente con el operador de propagación?
Tom Sarduy

44
@TomSarduy podría usar rest si desea especificar qué accesorios eliminar, por ejemplo const { b, ...picked } = object, crearía pickedcomo { a: 5, c: 7 }. Ha especificado simplemente eliminar b. Sin embargo, su eslint probablemente se molestará por declarar una var que no está usando.
Josh de Qaribou

24
Una desventaja aquí es que necesita escribir completamente la serie de nombres de atributos dos veces. Eso podría ser un gran problema en los casos en que se deben elegir muchos atributos.
Gershom

144

Sugiero echar un vistazo a Lodash ; Tiene muchas funciones de gran utilidad.

Por ejemplo pick(), sería exactamente lo que buscas:

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

violín


2
lo mismo para underscore.js
Dan

1
¿Hay alguna función para excluir solo ciertos campos en lugar de seleccionar? entonces tengo alrededor de 50 campos en mi json y quiero todo excepto solo 2 campos.
Shrikant Prabhu

77
¡Sí! puede usar _.omit(elmo, ['voice'])para devolver todo menosvoice
xavdid

lo que no me gusta de este enfoque es que ponga los nombres de los campos entre comillas para que sea susceptible a errores tipográficos, las refactorizaciones comunes como cambiar el nombre de una propiedad en su IDE no lo recogerán, etc.etc.
Andy

117

Si está utilizando ES6, hay una manera muy concisa de hacerlo mediante la desestructuración. La desestructuración le permite agregar fácilmente a los objetos usando una extensión, pero también le permite crear objetos de subconjuntos de la misma manera.

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

66
esto solo funciona para eliminar un campo, no para seleccionar un subconjunto conocido? potencialmente un número infinito de campos desconocidos para eliminar, pero podría ser lo que algunas personas están buscando
Alexander Mills,

Es cierto, pero puede eliminar varios campos conocidos que luego se pueden reasignar a un nuevo objeto, por lo que aún se siente relevante para esta pregunta. Añadido a la respuesta para ilustrar más.
Lauren

1
Esto es esencialmente lo mismo que la respuesta de @Ivan Nosov , aunque se explica de una manera más comprensible aquí
icc97

81

Si bien es un poco más detallado, puede lograr lo que todos los demás estaban recomendando subrayado / lodash durante 2 años, utilizando Array.prototype.reduce .

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

Este enfoque lo resuelve desde el otro lado: en lugar de tomar un objeto y pasarle nombres de propiedad para extraerlo, tome una matriz de nombres de propiedad y reduzca a un nuevo objeto.

Si bien es más detallado en el caso más simple, una devolución de llamada aquí es bastante útil, ya que puede cumplir fácilmente algunos requisitos comunes, por ejemplo, cambiar la propiedad 'color' a 'color' en el nuevo objeto, aplanar matrices, etc., cualquiera de Las cosas que debe hacer al recibir un objeto de un servicio / biblioteca y construir un nuevo objeto necesario en otro lugar. Si bien el subrayado / lodash son bibliotecas excelentes y bien implementadas, este es mi enfoque preferido para una menor dependencia del proveedor y un enfoque más simple y más consistente cuando mi lógica de creación de subconjuntos se vuelve más compleja.

editar: es7 versión de la misma:

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

editar: ¡Un buen ejemplo para curry también! Haga que una función 'pick' devuelva otra función.

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

Lo anterior es bastante similar al otro método, excepto que le permite construir un 'selector' sobre la marcha. p.ej

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

Lo que es especialmente interesante de este enfoque es que puede pasar fácilmente las 'elecciones' elegidas a cualquier cosa que tenga una función, por ejemplo Array#map:

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

3
es6 hace posible que esto sea aún más limpio a través de las funciones de flecha, y el retorno de Object.assign (ya que la asignación a una propiedad de objeto devuelve el valor de la propiedad, pero Object.assign devuelve el objeto).
Josh de Qaribou

Otra nota de es6: ya casi nunca necesitará hacer esto, ya que normalmente solo desestructura la asignación o los argumentos. por ejemplo function showToy({ color, height }) {, pondría solo lo que necesita en el alcance. El reduceenfoque tiene sentido principalmente cuando simplifica objetos para la serialización.
Josh de Qaribou

66
Esa versión ES6 es menos eficiente, porque hace una copia de todas las propiedades con cada iteración. Realiza una operación O (n) en O (n ^ 2). Un equivalente ES6 de su primer bloque de código seríaconst pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
4castle

@ 4castle sí llamada buena - no tiene sentido iterar tanto. Me gusta la sintaxis de coma, mejor que un montón de devoluciones.
Josh de Qaribou

1
@ShevchenkoViktor En realidad utilicé ese enfoque en mi versión original de es6, pero lo cambié después del comentario de @ 4castle. Creo que la propagación es más clara, pero es una gran diferencia para los objetos más grandes en el código que podrían estar fácilmente en un cuello de botella (por ejemplo, retrasar el procesamiento de los datos devueltos fetch), por lo que recomendaría agregar un comentario explicando el uso del operador de coma.
Josh de Qaribou

45

ES6 desestructuración

La sintaxis de desestructuración permite desestructurar y recombinar un objeto, ya sea con parámetros de función o variables.

La limitación es que una lista de claves está predefinida, no se pueden enumerar como cadenas, como se menciona en la pregunta. La desestructuración se vuelve más complicada si una clave no es alfanumérica, por ejemplo foo_bar.

La desventaja es que esto requiere duplicar una lista de claves, esto da como resultado un código detallado en caso de que una lista sea larga. Dado que la desestructuración de la sintaxis literal de objetos duplicados en este caso, una lista se puede copiar y pegar tal cual.

Lo bueno es que es una solución eficaz que es natural para ES6.

IIFE

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

Variables temporales

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

Una lista de cadenas

La lista arbitraria de claves seleccionadas consta de cadenas, según lo requiera la pregunta. Esto permite no predefinirlos y usar variables que contienen nombres clave, como pick(obj, 'foo', someKey, ...moreKeys).

Una línea se acorta con cada edición de 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] }), {});

O con operador de coma:

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

ES2019

ECMAScript 2017 tiene Object.entriesy Array.prototype.includes, ECMAScript 2019 tiene Object.fromEntries, se pueden rellenar cuando sea necesario y facilitar la tarea:

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

Un one-liner se puede reescribir como una función auxiliar similar a Lodashpick o omitdonde la lista de claves se pasa a travé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 }

Una nota sobre las llaves faltantes

La principal diferencia entre la desestructuración y la función convencional similar a Lodash pickes que la desestructuración incluye claves recogidas inexistentes con undefinedvalor en un subconjunto:

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

Este comportamiento puede o no ser deseable. No se puede cambiar por la sintaxis de desestructuración.

Mientras que pickse puede cambiar para incluir claves faltantes iterando una lista de claves elegidas en su lugar:

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
Qué pena que esta respuesta sea tan reciente y, por lo tanto, no obtenga la exposición que merece. En mi opinión, debería ser la respuesta aceptada para la integridad, simplicidad, versatilidad y, simplemente, trabajo. Mantendré la versión ES6 en mi biblioteca de fragmentos más útil.
VanAlbert

ES5 - "Error de referencia no
capturado

@NikhilVartak Se espera que la objvariable sea una variable que almacene un objeto. Si su nombre de variable difiere, úselo en lugar de obj.
Estus Flask

¡Culpa mía! Lo pasé por alto Object.keys(obj). Soñoliento.
Nikhil Vartak

34

No hay nada como eso integrado en la biblioteca principal, pero puede usar la desestructuración de objetos para hacerlo ...

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

También podría escribir una función de utilidad hacerlo ...

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

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

Las bibliotecas como Lodash también tienen _.pick().


1
genial, solo tuve que cambiar forEach a: keys.forEach (key => {newObject [key] = sourceObject [key];}) ;. Por favor, actualice el comentario si esto tiene sentido.
kandan

34

Estoy agregando esta respuesta porque ninguna de las respuestas utilizadas Comma operator.

Es muy fácil con destructuring assignmenty ,operador

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

console.log(picked);

Con una cierta mejora para la asignación de propiedades dinámicas, podría verse así:


2
mi mal, no sé lo que hice antes que no funcionó, pero parece que funciona
Ekkis

1
Esa es la mejor expresión, cuando se trata de la desestructuración
Mohamed Allal

1
Esta solución es inteligente, pero no funciona en modo estricto (es decir, 'use strict'). Me sale un ReferenceError: a is not defined.
kimbaudi

8
Tenga en cuenta que este enfoque contamina el alcance actual con dos variables ay c: tenga cuidado de no sobrescribir aleatoriamente variables locales o globales según el contexto. (La respuesta aceptada evita este problema mediante el uso de dos variables locales en una función en línea, que queda fuera del alcance después de la ejecución inmediata.)
mindplay.dk

2
La contaminación del espacio de nombres hace que esto sea poco práctico. Es extremadamente común tener variables en el alcance que coincidan con las propiedades del objeto, es por eso que existe la taquigrafía de apoyo + la desestructuración. Es muy probable que tenga altura o color ya definido como en el ejemplo original.
Josh de Qaribou

23

Una solución mas:

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

Esto me parece mucho más legible que casi cualquier respuesta hasta ahora, ¡pero tal vez solo soy yo!


99
Prefiero ser productivo a ser sofisticado pero un código confuso, y en la ingeniería de software de la vida real, esta es la solución más fácil de leer y mantener.
Janos

1
Sí, sin embargo, para mí, una ventaja de usar la notación de desestructuración y taquigrafía es que es menos propenso a errores. Si hubiera tenido un centavo por cada vez que he copiado y pegado un código para terminar subset = {color: elmo.color, height: elmo.color}, habría tenido al menos un ... bueno, quizás un centavo.
JHH

No llamaría a la taquigrafía desestructuradora menos propensa a errores, ya que no es SECO
gman

Tendría que estar de acuerdo. Sin contaminar el contexto con variables no deseadas, esta es, con mucho, la solución más fácil de leer. El resto parece demasiado confuso. Prefiero entender mi código en el momento en que lo miro.
Andrew

11

Puedes usar Lodash también.

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

Complementando, digamos que tiene una serie 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'}
    }
];

Si desea el mismo comportamiento, usando lodash, simplemente:

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

10

La desestructuración en variables dinámicamente nombradas es imposible en JavaScript como se discutió en esta pregunta .

Para establecer claves dinámicamente , puede usar la función reducir sin mutar el objeto de la siguiente manera:

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)

Sin embargo, debe tener en cuenta que está creando un nuevo objeto en cada iteración en lugar de actualizar un solo clon. - mpen

a continuación se muestra una versión que usa reducción con un solo clon (actualización del valor inicial pasado para reducir).

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
¡Increíble! Me arrojó por un momento sin darme cuenta de lo esencial que son los corchetes en [c]:. Supongo que de alguna manera está causando que c se vea como un valor en lugar del nombre de la propiedad. De todos modos, muy bien. +1
John Fairbanks

¡Gracias amigo! Ese uso es una cosa que me encanta de JavaScript, que habilita funciones genéricas sin usar eval. Simplemente, lo que hace es permitirle establecer una clave de dict a una variable en tiempo de ejecución. si define var key = 'someKey', puede usarlo como {[key]: 'value'}, que le proporciona {someKey: 'value'}. Realmente genial.
Muhammet Enginar

1
Debe tener en cuenta que está creando un nuevo objeto en cada iteración, en lugar de actualizar un solo clon.
mpen

1
@mpen buen hallazgo. He agregado una versión que muta un clon único, ya que has sugerido que también extienda los argumentos en lugar de pasar una matriz de claves.
Muhammet Enginar

7

Solución 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));
}

La información de escritura incluso permite la finalización automática:

¡Crédito para definitivamente escrito para U extends keyof Ttruco!


No funciona para mi
Nuthinking

@Nuthinking ¿Estás usando VS Code?
mpen

Sí, yo uso VS Code.
Nuthinking

@Nuthinking ¿Y lo pones en un archivo .ts? Eso debería funcionar. Acabo de intentarlo de nuevo
mpen


4

Solución dinámica

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


3

Solo otra forma ...

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]

Puede usar esta función con una matriz de objetos =)


2

Qué tal si:

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

Esto fallaría si el valor de la propiedad fuera false(o falso). jsfiddle.net/nfAs8
Alxandr

Por eso lo cambié a keys[i] in obj.
elclanrs

2

Esto funciona para mí en la consola de Chrome. ¿Algún problema con esto?

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

44
Esto se lee bien, pero crea dos variables innecesarias, color y altura.
user424174

No entiendas tu comentario. El requisito de OP era crear un objeto que tuviera esos dos elementos
MSi

@MSi Esto no solo crea un objeto, sino que también crea dos variables.
Diseño de Adrian el

1

Asignación de desestructuración con propiedades dinámicas

Esta solución no solo se aplica a su ejemplo específico, sino que se aplica de manera más general:

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

Puede definir fácilmente subset4etc. para tener más propiedades en cuenta.


1

Bueno viejo 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 respuesta usa el operador de coma mágico, también: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

si quieres ponerte realmente elegante, esto es más compacto:

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

Poniendo todo junto en una función reutilizable:

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. convertir argumentos a matriz

  2. utilizar Array.forEach()para elegir la propiedad

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

44
Extender el prototipo de los tipos nativos se considera una mala práctica, aunque funcionaría. No hagas esto si estás escribiendo una 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

Tratar

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

Agregando mis 2 centavos a Ivan Nosov respuesta de :

En mi caso, necesitaba muchas claves para ser 'cortadas' del objeto, por lo que se está volviendo feo muy rápido y no es una solución muy 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);

Así que aquí hay una solución 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: aunque la pregunta original fue para JavaScript, se puede hacer jQuery con la siguiente solución

puede extender jquery si lo desea, aquí está el código de muestra para un segmento:

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

aquí está el violín para lo mismo: http://jsfiddle.net/w633z/


16
¿Qué es jquery?
Christian Schlensker

1
@ChristianSchlensker es javascript
Kevin B
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.