2

Given the following two objects:

let a = { foo: 'bar' };

let b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

We can assume that all values within these objects are primitives. The goal is to copy some attributes from object b to object a, but not all.

Example:

a.one = b.one;
a.two = b.two;
// Do not copy item three
a.four = b.four;
a.five = b.five;
a.six = b.six;
// Do not copy item seven

In this example we have omitted attributes 3 and 7 but we could also omit any other attribute or even more than just two items. As you can see this solution can be quite cumbersome if we are dealing with many attributes.

Question: What's the shortest , cleanest and most simple solution to achieve something like this with modern JavaScript in 2021?

Note: For the solution you can assume there is an array of strings given. Each string represents a key of a value that should be copied. So, in the example above you can assume const keys = ['one', 'two', 'four', 'five', 'six']; being given.

Gabriel Lupu
  • 1,397
  • 1
  • 13
  • 29
Timo Ernst
  • 15,243
  • 23
  • 104
  • 165
  • Do you have some data structure that tells you what attributes you should or should not coppy? – Gabriel Lupu Apr 18 '21 at 13:39
  • @GabrielLupu You can assume there is an array of strings. Each string represents a key of a value that should be copied. So, in the example above you can assume `const keys = ['one', 'two', 'four', 'five', 'six'];` being given. – Timo Ernst Apr 18 '21 at 13:45

8 Answers8

4

I would use a forEach on the array of given keys to copy what I need:

const a = {
  foo: 'bar'
};

const b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

const doCopy = ['two', 'five'];

doCopy.forEach(el => a[el] = b[el]);

console.log(a);
Gabriel Lupu
  • 1,397
  • 1
  • 13
  • 29
2

The simplest (and shortest) is probably still a loop:

const res = {};
for (const key of keys) res[key] = source[key];

You can also write this using reduce:

const res = keys.reduce((target, key) => { target[key] = source[key]; return target }, {});
const res = keys.reduce((target, key) => Object.assign(target, {[key]: source[key]}), {});

Or, rather elegant, using Object.fromEntries:

const res = Object.fromEntries(keys.map(key => [key, source[key]]));

(this does however not support assigning to an existing object).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

This would be my solution. Even if it works I don't like having to import a dependency. Wondering if there is a better built-in solution to modern JavaScript.

util.js

export function copySelected(a, b, attrs) {
  for (let i=0; i < attrs.length; i+=1) {
    const attr = attrs[i];
    a[attr] = b[attr];
  }
  return a;
}

Usage:

import { copySelected } from './util';

let a = { foo: 'bar' };

let b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

copySelected(a, b, ['one', 'two', 'four', 'five', 'six']);
Timo Ernst
  • 15,243
  • 23
  • 104
  • 165
1

See comments:

const keys = ['one', 'two', 'four', 'five', 'six'];

let a = { foo: 'bar' };

let b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

// Loop over the array
keys.forEach(function(key){
  // If the b object contains the key specified in the array
  if(b[key]){
    a[key] = b[key];  // copy that key to a
  }
});

console.log(a);
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
1

You could use Object.assign to add the filtered (using the keys array to filter key/value pairs) entries from b to a:

const a = {
  foo: 'bar'
};

const b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

const keys = ['one', 'two', 'four', 'five', 'six'];

const res = Object
  .assign(a, Object.fromEntries(Object.entries(b)
    .filter(([k, v]) => keys.includes(k))
  ))
  
console.log(res)
Nick
  • 138,499
  • 22
  • 57
  • 95
1

You could assign the wanted properties.

a) without an array for assigning

let a = { foo: 'bar' },
    b = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7 },
    keys = ['one', 'two', 'four', 'five', 'six'];

keys.forEach(k => Object.assign(a, { [k]: b[k] }));

console.log(a);

b) with an array for assigning

let a = { foo: 'bar' },
    b = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7 },
    keys = ['one', 'two', 'four', 'five', 'six'];

Object.assign(a, ...keys.map(k => ({ [k]: b[k] })));

console.log(a);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Same idea as @Timo's answer, but with a map function and spread operator.

function copySelected(a, b, ...attrs) {
  attrs.map(x => a[x] = b[x]);
  return a;
}

let a = { foo: 'bar' };

let b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

console.log( copySelected(a, b, 'one', 'two', 'four', 'five', 'six'));
Gary
  • 13,303
  • 18
  • 49
  • 71
  • 1
    Abusing `Array.prototype.map` for an assignment is degrading readability, and adding overhead, for no reason. – ASDFGerte Apr 18 '21 at 14:03
0

Here is an example with ECMAScript 2021:

const b = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7
};

const copyFromThis = ['one','two']

const temp = Object.fromEntries(Object.entries(b).filter(([key , value])=>copyFromThis.includes(key) ))

console.log(temp)

you can customize it as you want!

Parham Heidari
  • 316
  • 3
  • 14