Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to copy object properties into new object? [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?

like image 846
Christian Schlensker Avatar asked Jul 22 '13 06:07

Christian Schlensker


7 Answers

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)
like image 154
Ivan Nosov Avatar answered Sep 21 '22 20:09

Ivan Nosov


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

like image 43
Ygg Avatar answered Sep 23 '22 20:09

Ygg


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

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))
);
like image 27
Estus Flask Avatar answered Sep 20 '22 20:09

Estus Flask


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'};
like image 22
Lauren Avatar answered Sep 21 '22 20:09

Lauren


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' },
// ]
like image 27
Josh from Qaribou Avatar answered Sep 21 '22 20:09

Josh from Qaribou


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);
like image 35
Code Maniac Avatar answered Sep 22 '22 20:09

Code Maniac


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

like image 38
alex Avatar answered Sep 23 '22 20:09

alex