如何获取javascript对象属性的子集


424

说我有一个对象:

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

我想用其属性的子集创建一个新对象。

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

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

我该如何实现?


2
Underscore库具有许多类似这样的帮助程序功能,请查看:underscorejs.org/#pick
Stefan

4
我以为emo.slice乍一看就这么说。
Bill Criswell,

1
再次考虑...我不会创建一个子集...
Andrew

Answers:


673

使用对象分解和属性速记

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

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


来自Philipp Kewisch:

这实际上只是一个即时调用的匿名函数。所有这些都可以在MDN 的“ 解构分配”页面上找到。这是扩展形式

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
您是如何得知的?在我见过的任何文档或文章(包括MDN)中,没有任何地方显示对象分解中使用的箭头语法。很高兴知道。
papiro'1

52
这实际上只是一个即时调用的匿名函数。所有这些都可以在MDN的“解构分配”页面上找到。这是扩展形式: 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
有没有一种方法可以使用传播算子动态地做到这一点?
Tom Sarduy

4
@TomSarduy如果要指定要删除的道具,例如const { b, ...picked } = object可以创建picked为,可以使用rest { a: 5, c: 7 }。您仅指定删除b。但是,由于您声明了您不使用的var,您的陪同可能会惹恼您。
来自Qaribou的Josh,17年

24
这里的一个缺点是您需要完全键入两次属性名称系列。在需要选择许多属性的情况下,这可能是个大问题。
Gershom

144

我建议看一看Lodash;它具有许多强大的实用程序功能。

例如pick(),正是您想要的:

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

小提琴


2
同样适用于underscore.js
2013年

1
有什么功能可以只排除某些字段而不是选择?所以我的json中大约有50个字段,除了2个字段外,我想要所有其他内容。
Shrikant Prabhu

7
是的!您可以_.omit(elmo, ['voice'])用来返回所有内容,但voice
xavdid

我对这种方法不满意的是,您将字段名放在引号中,这样容易受到拼写错误的影响,常见的重构(例如在IDE中重命名属性不会被选中等)等。
安迪

117

如果您使用的是ES6,则有一种非常简洁的方法来使用分解。分解使您可以轻松地使用跨度将对象添加到对象上,但是它也允许您以相同的方式制作子集对象。

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
这仅适用于删除字段,而不选择已知子集吗?可能会删除无限数量的未知字段,但这可能是某些人正在寻找的内容
亚历山大·米尔斯

是的,但是它可以删除几个已知字段,然后可以将这些字段重新分配给新对象,因此仍然感觉与此问题相关。添加到答案以进一步说明。
劳伦(Lauren)'18年

1
这基本上与@Ivan Nosov的答案相同,尽管此处以更易理解的方式进行了解释
icc97

81

虽然有点冗长,但是您可以使用Array.prototype.reduce完成 2年前其他人推荐的下划线/破折号。

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

这种方法从另一方面解决了问题:不要采用一个对象并将属性名称传递给它来进行提取,而是采用一组属性名称并将其简化为一个新对象。

尽管在最简单的情况下它比较冗长,但这里的回调很方便,因为您可以轻松满足一些常见要求,例如,在新对象上将'color'属性更改为'colour',展平数组等。从一个服务/库接收对象并构建其他地方需要的新对象时需要做的事情。下划线/破折号是出色的,实现良好的库,这是我首选的减少供应商依赖性的方法,当我的子集构建逻辑变得更加复杂时,这是更简单,更一致的方法。

编辑:es7版本相同:

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

编辑:也是一个不错的例子。有一个“ pick”功能返回另一个功能。

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

上面的方法与其他方法非常接近,除了它可以让您动态构建“选择器”。例如

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

这种方法特别整洁的是,您可以轻松地将所选的“小贴士”传递给具有功能的任何内容,例如Array#map

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

3
es6可以通过箭头功能使它变得更加干净,并且Object.assign的返回(由于分配给对象属性返回了属性值,而Object.assign返回了该对象。)
Josh从Qaribou

另一个es6注释:您几乎不再需要执行此操作,因为您通常只是解构分配或args。例如function showToy({ color, height }) {只会把您需要的东西放在范围内。reduce当您简化对象以进行序列化时,该方法主要有意义。
来自Qaribou的Josh,2016年

6
该ES6版本的性能较差,因为它会在每次迭代时复制所有属性。它使O(n)运算成为O(n ^ 2)。一个ES6等效于您的第一个代码块是const pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
4castle'2

@ 4castle是个好电话-没有那么多迭代。我喜欢逗号语法-比一堆回报更好。
来自Qaribou的Josh

1
@ShevchenkoViktor我实际上在我的原始es6版本中使用了这种方法,但是在@ 4castle的注释之后对其进行了更改。我认为分布更为清晰,但对于代码中较大的对象(可能会很容易成为瓶颈)(例如,延迟从中返回的呈现数据fetch)而言,这是一个巨大的差异,因此,我建议添加注释以解释逗号运算符的用法。
来自Qaribou的Josh,17年

45

ES6解构

分解语法允许使用函数参数或变量来分解和重​​组对象。

限制是键的列表是预定义的,正如问题所提到的,它们不能以字符串形式列出。如果键是非字母数字(例如),则解构将变得更加复杂foo_bar

缺点是,这需要重复键列表,如果列表很长,则会导致冗长的代码。由于在这种情况下解构重复对象的文字语法,因此可以照原样复制和粘贴列表。

好处是它是ES6固有的高性能解决方案。

国际教育展

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

临时变量

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

字符串列表

根据问题的要求,选择的键的任意列表由字符串组成。这样就可以不预定义它们,而使用包含键名的变量,例如pick(obj, 'foo', someKey, ...moreKeys)

每个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] }), {});

或使用逗号运算符:

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

ES2019

ECMAScript 2017具有Object.entriesArray.prototype.includes,ECMAScript 2019具有Object.fromEntries,可以在需要时进行填充,使任务更轻松:

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

可以将单行代码重写为类似于Lodash的pick辅助函数,或者omit将键列表通过参数传递:

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 }

关于遗失钥匙的注意事项

解构和传统的类似于Lodash的pick函数之间的主要区别在于,解构包括undefined子集中不存在带有值的选取键:

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

此行为可能是或不希望的。不能为解构语法而更改它。

pick可以通过迭代选择的键列表来将While 更改为包括丢失的键:

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
这个答案如此之新,以至于没有得到应有的曝光,真是太可惜了。IMO应该是完整性,简单性,多功能性和公正性的公认答案。我会将ES6版本保留在最有用的代码段库中。
VanAlbert

ES5-“未捕获的ReferenceError:未定义obj”
Nikhil Vartak

@NikhilVartak预期obj变量是存储对象的变量。如果您的变量名称不同,请使用它代替obj
Estus Flask

我的错!我忽略了Object.keys(obj)。困。
Nikhil Vartak

34

核心库中没有内置的东西,但是您可以使用对象分解来实现。

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

您也可以编写一个实用程序函数来做...

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

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

Lodash等图书馆也有_.pick()


1
太好了,我只需要将forEach更改为:keys.forEach(key => {newObject [key] = sourceObject [key];});。如果有必要,请更新评论。
肯丹'19

34

我添加此答案是因为没有使用任何答案Comma operator

destructuring assignment,操作符非常简单

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

console.log(picked);

通过对动态属性分配进行某种增强,它看起来可能像这样:


2
我的坏,我不知道之前我做了什么,但它没有用,但是它确实起作用了
ekkis

1
这是最好的表达方式,涉及到
重组

1
此解决方案很聪明,但在严格模式下(即'use strict')不起作用。我得到一个ReferenceError: a is not defined
kimbaudi

8
请注意,此方法会使用两个变量来污染当前范围,a并且c-注意不要根据上下文随机覆盖局部或全局变量。(公认的答案通过
在内

2
名称空间污染使得这完全不切实际。在作用域中已经存在与对象属性匹配的变量是非常普遍的,这就是为什么存在prop速记+解构的原因。很有可能您已经定义了高度或颜色,如原始示例中所示。
从Qaribou出发的Josh

23

另一种解决方案:

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

对于我来说,这看起来比到目前为止的任何答案都更具可读性,但是也许就是我!


9
我更喜欢生产力,而不是花哨的代码,但在现实生活中的软件工程中,这是最易读和可维护的解决方案。
Janos '18

1
是的,但是,对我而言,使用解构和速记符号的一个好处是它不易出错。如果每次我错误地复制并粘贴代码以结束时每次都会得到一分钱subset = {color: elmo.color, height: elmo.color},那么我至少会得到……好吧,也许是一角钱。
JHH

我不会称其为破坏性的简写形式,因为它不是DRY形式,因此不容易出错
gman

我必须同意。在不使用不需要的变量污染上下文的情况下,这是迄今为止最易读的解决方案。其余的看起来太混乱了。我宁愿在看代码的那一刻就理解我的代码。
安德鲁

11

您也可以使用Lodash

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

作为补充,假设您有一个数组“ elmo”:

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

如果您希望使用lodash进行相同的操作,则只需:

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

10

如本问题所述,在JavaScript中无法将其分解为动态命名的变量。

动态设置键,可以使用reduce函数,而无需更改对象,如下所示:

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)

应该注意的是,尽管不是更新单个克隆,但是您在每次迭代时都创建一个新对象。– mpen

以下是使用带有单个克隆的reduce的版本(更新传递给reduce的初始值)。

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
太棒了!这让我一瞬间没意识到括号在[c]上的重要性。我假设这某种程度上导致c被视为一个值而不是属性的名称。无论如何,非常酷。+1
约翰·费尔班克斯

谢了哥们!这种用法是我喜欢JavaScript的一件事,它无需使用eval就可以启用泛型函数。简而言之,它的作用是让您在运行时将dict键设置为变量。如果您定义var key ='someKey',则可以将其用作{[key]:'value'},从而得到{someKey:'value'}。真的很酷。
Muhammet Enginar '18

1
应该注意的是,尽管不是更新单个克隆,但是您在每次迭代时都创建一个新对象。
mpen

1
@mpen很好找到。我添加了一个版本,用于更改单个克隆,因为您建议也传播args而不是传递键数组。
穆罕默德·恩吉纳尔

7

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

键入信息甚至允许自动完成:

信贷DefinitelyTypedU extends keyof T把戏!


不为我工作。
Nuthinking

@Nuthinking您在使用VS Code吗?
mpen

是的,我确实使用VS Code。
Nuthinking

@Nuthinking然后将其放在.ts文件中?那应该工作。我刚刚再次尝试过
mpen


4

动态解决方案

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


3

换一种方式...

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]

您可以将此函数与Objects =)数组一起使用


2

怎么样:

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

如果该属性的值是false(或伪造的),这将失败。jsfiddle.net/nfAs8
Alxandr

这就是为什么我将其更改为keys[i] in obj
elclanrs

2

这在Chrome控制台中对我有效。有什么问题吗?

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

4
这看起来不错,但会创建两个不必要的变量:颜色和高度。
user424174

不明白您的评论。OP的要求是创建一个包含这两个元素的对象
-MSi

@MSi这不仅创建一个对象,还创建两个变量。
由Adrian设计,

1

具有动态属性的销毁分配

此解决方案不仅适用于您的特定示例,而且更适用于:

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

您可以轻松定义subset4等以考虑更多属性。


1

好旧的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);

这个答案还使用了神奇的逗号运算符,也:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

如果您真的想花哨的话,这会更紧凑:

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

将它们放到一个可重用的函数中:

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. 将参数转换为数组

  2. 用于Array.forEach()挑选物业

    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
扩展本机类型的原型被认为是不好的做法,尽管它可以工作。如果要编写库,请不要这样做。
Emile Bergeron'5

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

尝试

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

将2美分加到Ivan Nosov的答案中:

以我为例,我需要从对象中“切出”许多键,因此它变得非常丑陋,而且不是一种动态解决方案:

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

所以这是一个使用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

注意:尽管最初提出的问题是针对javascript的,但可以通过以下解决方案完成jQuery

您可以扩展jquery,如果您想获得一个切片的示例代码:

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

这是相同的小提琴:http : //jsfiddle.net/w633z/


16
什么是jQuery?
Christian Schlensker

1
@ChristianSchlensker,它是Javascript
Kevin B,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.