Trying to recursively hash values in object - javascript

friends. I'm trying to write code that hashes all values in a JSON file, regardless of file structure, while preserving the keys and structure. I'm new to javascript, and am having some trouble. My code hashes the values of big and baz, but doesn't recursively hash the values of cat and bar like I want it to. Ideally, I want the numbers hashed and the names (big, foo, etc.) left alone. Thank you so much! See my code below:
var meow = {
big: 20,
baz: {
foo: {
cat: 3,
bar: 5
}
}
};
console.log(typeof(meow.baz.foo));
function hashobj(obj)
{
var valarray = Object.keys(obj);
var zer = valarray[0];
for(var i = 0; i < valarray.length; i++)
{
var vaz = valarray[i];
if(typeof(obj[vaz] != "object"))
{
obj[vaz] = sha256(obj[vaz] + buf);
}
if(typeof(obj[vaz]) == "object")
{
console.log("HERE");
hashobj(obj[vaz]);
}
}
}
hashobj(meow);
console.log(meow);

If you're looking to do this recursively, I would suggest using a generic transformation function that handles the recursive object structure and delegates to a supplied function the actual work of transforming the leaf nodes.
In this version, the transform function does all the heavy lifting. It calls the supplied function on scalar values and recursively calls itself on objects and arrays, recreating the structure of the original with the new values. This is quite reusable.
We create our hashObject function by passing transform a function which does the sha256 encoding of our values.
const transform = (fn) => (obj) =>
Array.isArray (obj)
? obj .map (transform (fn))
: Object (obj) === obj
? Object .fromEntries (Object .entries (obj)
.map (([k, v]) => [k, transform (fn) (v)])
)
: fn (obj)
const hashObj = transform ((n) => sha256 (String (n)))
const meow = {big: 20, baz: {foo: {cat: 3, bar: 5, qux: [1, 2, 3]}}};
// added to demonstrate arrays --------^
console .log (hashObj (meow))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://unpkg.com/js-sha256#0.9.0/src/sha256.js"></script>

Scott's answer is wonderful. The optional chaining operator, ?., is supported most places now and is particularly useful for runtime type checking. I'm sharing this post as a way to see transform expressed using this modern feature -
function transform (f, o)
{ switch (o?.constructor) // <- any o, even null and undefined
{ case Array:
return o.map(_ => transform(f, _))
case Object:
return Object.fromEntries
( Object
.entries(o)
.map(([k, _]) => [k, transform(f, _)])
)
default:
return f(o)
}
}
const result =
transform
( _ => sha256(String(_))
, {big: 20, baz: {foo: {cat: 3, bar: 5, qux: [1, 2, 3]}}}
)
console.log(result)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://unpkg.com/js-sha256#0.9.0/src/sha256.js"></script>
{
"big": "f5ca38f748a1d6eaf726b8a42fb575c3c71f1864a8143301782de13da2d9202b",
"baz": {
"foo": {
"cat": "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce",
"bar": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d",
"qux": [
"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",
"d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
"4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce"
]
}
}
}
One distinct advantage to this approach is the Array and Object branches can appear in any order. When using Array.isArray(t) it must be checked before checking Object(t) === t. It's a subtle thing but is worth noting -
// also correct!
function transform (f, o)
{ switch (o?.constructor)
{ case Object: // <- type-check Object
return // ...
case Array: // <- type-check Array
return // ...
default:
return f(o)
}
}
You may also wish to hash an entire object. Here's one possibility to implement a generic hash using a generic traverse function -
function* traverse (t, r = [])
{ switch (t?.constructor) // <- any t, even null and undefined
{ case Array:
case Set:
case Map:
for (const [k, _] of t.entries())
yield* traverse(_, [...r, k])
break
case Object:
for (const [k, _] of Object.entries(t))
yield* traverse(_, [...r, k])
break
default:
yield [r, t]
}
}
function hash (t)
{ const r = sha256.create()
for (const [k, v] of traverse(t))
r.update(k.concat(v).join(":"))
return r.hex()
}
const input =
{big: 20, baz: {foo: {cat: 3, bar: 5, qux: [1, 2, 3]}}}
console.log(hash("foo"), hash("foo"))
console.log(hash([1,2,3]), hash([1,2,3]))
console.log(hash(input), hash(input))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://unpkg.com/js-sha256#0.9.0/src/sha256.js"></script>
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
492f06976c8bc705819f5d33d71be6a80a547b03f87c377e3543605d8260159c
492f06976c8bc705819f5d33d71be6a80a547b03f87c377e3543605d8260159c
d1ae8b8641d3d6d65b1e4eecab0484a9f9618f2aabafe473c8bb0b4f6382695c
d1ae8b8641d3d6d65b1e4eecab0484a9f9618f2aabafe473c8bb0b4f6382695c

Everything is ok but the parenthesis:
if(typeof(obj[vaz] != "object"))
should read:
if(typeof(obj[vaz]) != "object")

Related

How to properly filter Object by values in Array [duplicate]

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
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)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (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;
}, {});
Omit (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;
}, {});
Pick (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), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (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]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
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))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
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'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
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;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
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] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "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'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
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)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to 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)
Dynamic solution
['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']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
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)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
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]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
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');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
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"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
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);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old 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);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
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);
I've got the same problem and solved it easily by using the following libs:
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' }
I know it isn't the cleanest, but it's simple and easy to understand.
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;
};
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");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
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}
);
You can easily define subset4 etc. to take more properties into account.

How does one incrementally build a nested object structure from an array of property names?

My task is straight forward. I have an array of strings:
let a=["a","b","c"];
And i want to convert that array to (can alter the original array, doesn't matter) what i would like to call as "recursive object" just so:
//in json format just to demonstrate
"object": {
"a": {
"b":{
"c":{
}
}
}
}
I've tried the following logic but couldn't get it to work due to reference problem and i couldn't build recursion.
let tempObj;
let obj2;
array.slice().reverse().forEach(function(item,index){
obj2=tempObj;
tempObj[item]="";
});
Just to make sure we are on the same page, another example:
let arr=["alpha","beta","gamma"];
let magicObj=someMagicFunction(arr);
//magicObj["alpha"]["beta"]["gamma"] contains the value ""
Thanks
Start aggregating your object from the array's right most side via reduceRight and provide e.g. an empty object / {} or an empty string / "" as this method's initial value(s) ...
console.log(
'object :',
["a","b","c"]
.reduceRight((obj, key) =>
({ [key]: obj }), {}
)
);
console.log(
'object :',
["alpha","beta","gamma"]
.reduceRight((obj, key) =>
({ [key]: obj }), ""
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
... and since code code-reuse always should be a goal the above examples change to ...
function createObjectWithParentKeyAndChildValue(value, key) {
return { [key]: value };
}
console.log(
'object :',
['a', 'b', 'c']
.reduceRight(createObjectWithParentKeyAndChildValue, {})
);
console.log(
'object :',
['alpha', 'beta', 'gamma']
.reduceRight(createObjectWithParentKeyAndChildValue, '')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Here is a simple solution:
const arr = ["a","b","c"];
const result = arr.reverse().reduce((obj, key) => ({[key]: obj}), {})
console.log(result)
Here a little explaination:
o is the result of the last iteration and v is the current element in the array. {[v]: o} creates a new object and sets the property v to o and returns that.
The reduce / reduceRight answers are great. But this can also be done with a fairly trivial recursive version:
const buildDeep = ([p, ...ps], val) =>
p == undefined ? val : {[p]: buildDeep (ps, val)}
console .log (buildDeep (['a', 'b', 'c'], {}))
console .log (buildDeep (['alpha', 'beta', 'gamma'], ''))
To my mind, this is even simpler than reduce. It feels related to the various path-setting functions you see around, but is less complex since it doesn't have to work with an existing object.
there is my pure recursive answer:
let a=["a","b","c"];
const b = (arr = [], obj = null) => {
if (arr.length > 0) {
const { length, [length - 1]: last, ...r } = arr;
const rest = Object.values(r);
const nextObj = obj ? { [last]: obj } : { [last]: {} };
return b(rest, nextObj);
}
return obj;
};
console.log(b(a));
let magicObj = arr.reverse().reduce((obj, prop) => ({ [prop]: obj }), {})

Convert class to array with filtered fields in JavaScript [duplicate]

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
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)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (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;
}, {});
Omit (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;
}, {});
Pick (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), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (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]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
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))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
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'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
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;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
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] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "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'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
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)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to 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)
Dynamic solution
['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']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
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)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
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]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
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');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
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"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
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);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old 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);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
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);
I've got the same problem and solved it easily by using the following libs:
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' }
I know it isn't the cleanest, but it's simple and easy to understand.
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;
};
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");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
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}
);
You can easily define subset4 etc. to take more properties into account.

How to filter collection according to array values when compared with collection keys [duplicate]

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
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)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (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;
}, {});
Omit (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;
}, {});
Pick (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), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (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]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
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))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
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'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
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;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
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] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "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'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
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)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to 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)
Dynamic solution
['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']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
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)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
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]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
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');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
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"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
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);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old 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);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
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);
I've got the same problem and solved it easily by using the following libs:
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' }
I know it isn't the cleanest, but it's simple and easy to understand.
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;
};
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");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
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}
);
You can easily define subset4 etc. to take more properties into account.

Destructuring and cleaning up my variables in React [duplicate]

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
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)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (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;
}, {});
Omit (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;
}, {});
Pick (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), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (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]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
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))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
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'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
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;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
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] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "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'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
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)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to 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)
Dynamic solution
['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']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
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)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
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]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
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');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
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"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
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);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old 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);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
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);
I've got the same problem and solved it easily by using the following libs:
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' }
I know it isn't the cleanest, but it's simple and easy to understand.
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;
};
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");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
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}
);
You can easily define subset4 etc. to take more properties into account.

Categories

Resources