[javascript] How to get a subset of a javascript object's properties

ES6 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 the question mentions. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo_bar.

The downside is that this requires to duplicate a list of keys, 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.

The upside is that it's performant solution that is natural to ES6.

IIFE

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

Temporary variables

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

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, like pick(obj, 'foo', someKey, ...moreKeys).

A one-liner becomes shorter with each JS edition.

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

Or with comma operator:

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

ES2019

ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed and make the task easier:

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

A one-liner can be rewritten as helper function similar to Lodash pick or omit where the list of keys is passed through arguments:

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 }

A note about missing keys

The major difference between destructuring and conventional Lodash-like pick function is that destructuring includes non-existent picked keys with undefined value in a subset:

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

This behaviour may or not be desirable. It cannot be changed for destructuring syntax.

While pick can be changed to include missing keys by iterating a list of picked keys instead:

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 }