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

假设我有一个对象:

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

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

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


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

我怎样才能做到这一点?

460044 次浏览

核心库没有类似的内置功能,但您可以使用对象解构来做到这一点……

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

不如:

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

我建议看看Lodash;它有很多很棒的实用函数。

例如pick()正是您所寻求的:

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

小提琴

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

注意:虽然最初的问题是针对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/

您也可以使用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'}
}
];

如果你想要同样的行为,使用洛达什,你只需:

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

虽然它有点冗长,但你可以通过使用Array.prototype.reduce来完成其他人两年前推荐的下划线/洛塔什。

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

这种方法从另一方面解决了这个问题:不是获取一个对象并将属性名称传递给它来提取,而是获取一个属性名称数组并将它们缩减为一个新对象。

虽然在最简单的情况下会更冗长,但这里的回调非常方便,因为你可以轻松满足一些常见的要求,例如在新对象上将“颜色”属性更改为“颜色”,展平数组等-当从一个服务/库接收对象并在其他地方构建需要的新对象时需要做的任何事情。虽然下划线/洛塔什是优秀的、实现良好的库,但这是我减少对供应商依赖的首选方法,当我的子集构建逻辑变得更加复杂时,这是一种更简单、更一致的方法。

编辑:es7版本相同:

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

编辑:柯里化的一个很好的例子!让一个“选择”函数返回另一个函数。

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' },
// ]

使用对象解构和属性速记

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)

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

具有动态属性的解构赋值

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

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等以考虑更多属性。

如果您使用的是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'};

这对我Chrome控制台有用。有问题吗?

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

只是另一种方式…

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]

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

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

应该注意,您在每次迭代中都创建了一个新对象,而不是更新单个克隆

下面是一个使用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)

还有一个解决方案:

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

对我来说,这看起来比迄今为止几乎任何答案都更具可读性,但也许那只是我!

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

如果您已经在使用,请使用Lodash库的pick方法。

var obj = { 'a': 1, 'b': '2', 'c': 3 };


_.pick(object, ['a', 'c']);


// => { 'a': 1, 'c': 3 }

https://lodash.com/docs/4.17.10#pick

我添加这个答案是因为没有一个答案使用Comma operator

使用destructuring assignment,运算符非常容易

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


console.log(picked);

动态解决方案

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

let subset= (obj,keys)=> keys.reduce((a,b)=> (a[b]=obj[b],a),{});




// TEST


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


console.log( subset(elmo, ['color', 'height']) );

试试看

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

TypeScript解决方案:

function pick<T extends object, U extends keyof T>(
obj: T,
paths: Array<U>
): Pick<T, U> {
const ret = Object.create(null);
for (const k of paths) {
ret[k] = obj[k];
}
return ret;
}

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

归功于定义类型U extends keyof T技巧!

TypeScript Playground

两种常见的方法是解构和传统的类似Lodash的pick/omit实现。它们之间的主要实际区别是解构需要一个静态的键列表,不能省略它们,包括不存在的挑选的键,即它是包容的。这可能是可取的,也可能不是可取的,不能因为解构语法而改变。

给定:

var obj = { 'foo-bar': 1, bar: 2, qux: 3 };

常规拾取foo-barbarbaz键的预期结果:

{ 'foo-bar': 1, bar: 2 }

包容拣选的预期结果:

{ 'foo-bar': 1, bar: 2, baz: undefined }

破坏

解构语法允许使用函数参数或变量解构和重新组合对象。

限制是键列表是预定义的,它们不能像问题中描述的那样列为字符串。如果键是非字母数字的,则解构会变得更加复杂,例如foo-bar

好处是它是ES6自然的高性能解决方案。

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

IIFE

const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);

临时变量

const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };

一个字符串列表

根据问题的要求,选择的键的任意列表由字符串组成。这允许不预定义它们并使用包含键名['foo-bar', someKey, ...moreKeys]的变量。

ECMAScript 2017有Object.entriesArray.prototype.includes,ECMAScript 2019有Object.fromEntries,它们可以在需要时进行多边形填充。

单行

考虑到要选择的对象包含额外的键,从列表中迭代键通常比对象键更有效,如果需要省略键,反之亦然。

选择(ES5)

var subset = ['foo-bar', 'bar', 'baz']
.reduce(function (obj2, key) {
if (key in obj) // line can be removed to make it inclusive
obj2[key] = obj[key];
return obj2;
}, {});

省略(ES5)

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

拨片(ES6)

const subset = ['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

省略(ES6)

const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

选择(ES2019)

const subset = Object.fromEntries(
['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.map(key => [key, obj[key]])
);

省略(ES2019)

const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);

可重用函数

单行程序可以表示为类似于Lodashpickomit的可重用辅助函数,其中键列表通过参数pick(obj, 'foo-bar', 'bar', 'baz')传递。

const pick = (obj, ...keys) => Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
);


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


const omit = (obj, ...keys) => Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key))
);

将我的2美分添加到伊万·诺索夫答案:

在我的例子中,我需要将许多键从对象中“切片”出来,所以它变得非常丑陋,而且不是一个非常动态的解决方案:

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

因此,这里有一个动态的解决方案,它使用ava:

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

我有同样的问题,并通过使用以下库轻松解决了它:

object.pick

https://www.npmjs.com/package/object.pick

pick({a: 'a', b: 'b', c: 'c'}, ['a', 'b'])
//=> {a: 'a', b: 'b'}

object.omit

https://www.npmjs.com/package/object.omit

omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c'])
//=> { b: 'b' }

我想在这里提到非常好的策展:

pick-es2019.js

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

pick-es2017.js

Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});

pick-es2015.js

Object.keys(obj)
.filter((key) => ['whitelisted', 'keys'].indexOf(key) >= 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})

omit-es2019.js

Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
);

omit-es2017.js

Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});

omit-es2015.js

Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})

使用带有速记对象文字语法的“with”语句

还没有人演示过这种方法,可能是因为它很糟糕,你不应该这样做,但我觉得它必须被列出。

var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output =  {a,b,f}
}
console.log(output)

优点:您不必输入两次属性名称。

缺点:由于许多原因,不推荐使用“”语句。

结论:它工作得很好,但不要使用它。

添加另一种深奥的方式,这也有效:

var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}

但是你必须写两次道具名称。

对象的数组

const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]

制作一个或多个对象的子集可以通过以这种方式解构对象来实现。

const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));

我发现的最简单的方法,它不会创建不必要的变量,是一个你可以调用的函数,它的工作方式与洛达什相同,如下所示:

pick(obj, keys){
return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}

例如:

pick(obj, keys){
return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
const obj = {a:1, b:2, c:3, d:4}
const keys = ['a', 'c', 'f']
const picked = pick(obj,keys)
console.log(picked)

pick = (obj, keys) => {
return Object.assign({}, ...keys.map(key => ({
[key]: obj[key]
})))
}


const obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
const keys = ['a', 'c', 'f']


const picked = pick(obj, keys)
console.log(picked)

像这篇文章中的一些人一样,我同意evert的观点,即最明显的老派方法实际上是最好的方法,但是为了好玩,让我提供另一种在某些情况下不明智的方法,比如当你已经定义了你的子集,并且你想从另一个包含其属性的超集或相交集的对象复制属性到它。

let set = { a : 1, b : 2, c : 3 };
let subset = { a : null, b : null };
try {
Object.assign(Object.seal(subset), set);
} catch (e) {
console.log('its ok I meant to do that <(^.^)^');
}
console.log(subset);

扩展内置原型有其优点。如果你使用Object.defineProperty,你不会产生任何污染——唯一剩下的问题将是与未来的属性冲突(例如,你定义了Object.prototype.slice,在未来的ES标准将Object.prototype.slice定义为具有不同的功能——现在你的代码正在破坏应该存在于Object.prototype.slice的预期功能)。

还有……………… Elmo的身高并不陌生(!!!)

Object.defineProperty(Object.prototype, 'slice', {
enumerable: false,
writable: true,
value: function(...args) {
let o = {};
for (let k of args) this.hasOwnProperty(k) && (o[k] = this[k]);
return o;
}
});


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


console.log(elmo.slice('color', 'height'));


console.log('Look, no pollution:');
for (let k in elmo) console.log(`- ${k}`);

我知道这不是最干净的,但它很简单,很容易理解。

function obj_multi_select(obj, keys){
let return_obj = {};
for (let k = 0; k < keys.length; k++){
return_obj[keys[k]] = obj[keys[k]];
};
return return_obj;
};

如果您想保留比要删除的属性更多的属性,您可以使用rest参数语法:

const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}

我认为这是你的答案(以及所有正在寻找它的人)。

const object = { a: 5, b: 6, c: 7  };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }

您可以使用逗号操作符

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


const subset = ({color , height} = elmo , {color , height});
// {color: 'red', height: 'unknown'}

值得注意的是,默认情况下,Zod模式将去掉未知属性。如果您已经在使用Zod,这可能适合您的开发过程。

https://github.com/colinhacks/zod

import { z } from "zod";


// muppet schema
const muppet = z.object({
color: z.string(),
annoying: z.boolean(),
height: z.string(),
meta: z.object({ one: z.string(), two: z.string() }),
});


// TypeScript type if you want it
type TMuppet = z.infer<typeof muppet>;


// elmo example
const elmo: TMuppet = {
color: "red",
annoying: true,
height: "unknown",
meta: { one: "1", two: "2" },
};


// get a subset of the schema (another schema) if you want
const subset = muppet.pick({ color: true, height: true });


// parsing removes unknown properties by default
subset.parse(elmo); // { color: 'red', height: 'unknown' }