Object.assign—override nested property

JavascriptEcmascript 6

Javascript Problem Overview


I have an Object a like that:

const a = {
  user: {
   …
   groups: […]
   …
  }
}

whereby there are a lot more properties in a.user

And I would like to change only the a.user.groups value. If I do this:

const b = Object.assign({}, a, {
  user: {
    groups: {}
  }
});

b doesn't have any other Property except b.user.groups, all others are deleted. Is there any ES6 way to only change the nested property, without loosing all the other, with Object.assign?

Javascript Solutions


Solution 1 - Javascript

After some trying I could find a solution that looks pretty nice like that:

const b = Object.assign({}, a, {
  user: {
    ...a.user,
    groups: 'some changed value'
  }
});

To make that answer more complete here a tiny note:

const b = Object.assign({}, a)

is essentially the same as:

const b = { ...a }

since it just copies all the properties of a (...a) to a new Object. So the above can written as:

 const b = {
   ...a,          //copy everything from a
   user: {        //override the user property
      ...a.user,  //same sane: copy the everything from a.user
      groups: 'some changes value'  //override a.user.group
   }
 }

Solution 2 - Javascript

Here's a small function called Object_assign (just replace the . with a _ if you need nested assigning)

The function sets all target values by either pasting the source value in there directly, or by recursively calling Object_assign again when both the target value and source value are non-null objects.

const target = {
  a: { x: 0 },
  b: { y: { m: 0, n: 1      } },
  c: { z: { i: 0, j: 1      } },
  d: null
}

const source1 = {
  a: {},
  b: { y: {       n: 0      } },
  e: null
}

const source2 = {
  c: { z: {            k: 2 } },
  d: {}
}

function Object_assign (target, ...sources) {
  sources.forEach(source => {
    Object.keys(source).forEach(key => {
      const s_val = source[key]
      const t_val = target[key]
      target[key] = t_val && s_val && typeof t_val === 'object' && typeof s_val === 'object'
                  ? Object_assign(t_val, s_val)
                  : s_val
    })
  })
  return target
}

console.log(Object_assign(Object.create(target), source1, source2))

Solution 3 - Javascript

You asked specifically for an ES6 way with Object.assign, but maybe someone else will prefer my more 'general' answer - you can do it easily with lodash, and personally I think that this solution is more readable.

import * as _ from 'lodash';
_.set(a, 'user.groups', newGroupsValue);

It mutates object.

Solution 4 - Javascript

You can change it this way,

const b = Object.assign({}, a, {
  user: Object.assign({}, a.user, {
          groups: {}
        })
});

Solution 5 - Javascript

> The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.

assign method will make a new object by copying from the source object, the issue if you change any method later on in the source object it will not be reflected to the new object.

Use create()

> The Object.create() method creates a new object with the specified prototype object and properties.

const b = Object.create(a)
b.user.groups = {}
// if you don't want the prototype link add this
// b.prototype = Object.prototype 

This way you have b linked to a via the prototype and if you make any changes in a it will be reflected in b, and any changes in b will not affect a

Solution 6 - Javascript

More general solution below. It makes recursively assigning only if there are conflicts (same key names). It resolves issues assigning complex objects, which has many reference points. For example, nested-object-assign, an existing package for nested object assign in npm, stuck with complex objects assign because it recursively goes through all keys.

var objecttarget = {
  a: 'somestring',
  b: {x:2,y:{k:1,l:1},z:{j:3,l:4}},
  c: false,
  targetonly: true
};

var objectsource = {
  a: 'somestring else',
  b: {k:1,x:2,y:{k:2,p:1},z:{}},
  c: true,
  sourceonly: true
};

function nestedassign(target, source) {
  Object.keys(source).forEach(sourcekey=>{
    if (Object.keys(source).find(targetkey=>targetkey===sourcekey) !== undefined && typeof source[sourcekey] === "object") {
      target[sourcekey]=nestedassign(target[sourcekey],source[sourcekey]);
    } else {
      target[sourcekey]=source[sourcekey];
    }
  });
  return target;
}

// It will lose objecttarget.b.y.l and objecttarget.b.z
console.log(Object.assign(Object.create(objecttarget),objectsource));

// Improved object assign
console.log(nestedassign(Object.create(objecttarget),objectsource));

Solution 7 - Javascript

Small fine tune of phillips first answer.

const object1 = {
  abc: {
  	a: 1
  }
};

const object2 = {
  abc: {
  	b: 2
  }
};

Object.assign(object1, {
	abc: {
    	...object1.abc,
      	...object2.abc
    }
});

console.log(object1);
// Object { abc: Object { a: 1, b: 2 } }

Solution 8 - Javascript

A working version which supports nested Arrays as well

const target = {
   var1: "...",
   var2: {
       nestVar1: "...",
       nestVar2: [...]   
       }
}

const source = {
  var2: {
      nestVar3: { ... }
      nestVar4: "...",
      nestVar5: [...]
      },
  var3: "...",
  var4: [...]
}

the Function:

function objectAssign(target, source) {
    Object.keys(source).forEach(sourceKey => {
      if(Object.keys(source).find(targetKey => targetKey === sourceKey) !== undefined && typeof source[sourceKey] === "object") {
        target[sourceKey] = objectAssign(target[sourceKey], source[sourceKey]);
      } else if(!Array.isArray(source)) {
        target[sourceKey] = source[sourceKey];
      } else {
        target = source;
      }
    });
    return target;
}

then

let newObj = objectAssign({}, target, source);

Solution 9 - Javascript

Here is my NestedObjectAssign() contribution, because I have seen a function being duplicated across posts, and it does not work good.

This version just works as expected, as shown in example:

function NestedObjectAssign(target, source) 
{
	Object.keys(source).forEach(sourceKey => 
	{
		if (typeof source[sourceKey] === "object") 
		{
			target[sourceKey] = NestedObjectAssign(target[sourceKey] || {}, source[sourceKey])
		}
		else 
		{
			target[sourceKey] = source[sourceKey]
		}
	})

	return target
}

var target = 
{
	stringTarget: "target",
	stringCommon: "targetCommon",
	nestedCommon: 
	{
		target: 1,
		common: 1,
		nestedTarget: 
		{
			target: 1,
			common: 1
		}
	}
}

var source = 
{
	stringSource: "source",
	stringCommon: "sourceCommon",
	nestedCommon: 
	{
		source: 2,
		common: 3,
		nestedSource: 
		{
			source: 2,
			common: 3
		}
	}
}

var t = NestedObjectAssign({}, target)
var s = NestedObjectAssign({}, source)
var targetSource = NestedObjectAssign(t, s)

console.log({ target })
console.log({ source })
console.log({ targetSource })

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionphilippView Question on Stackoverflow
Solution 1 - JavascriptphilippView Answer on Stackoverflow
Solution 2 - JavascriptGust van de WalView Answer on Stackoverflow
Solution 3 - JavascriptPatryk WlaźView Answer on Stackoverflow
Solution 4 - JavascriptpratZView Answer on Stackoverflow
Solution 5 - JavascriptAbdullah AlsigarView Answer on Stackoverflow
Solution 6 - JavascriptWonView Answer on Stackoverflow
Solution 7 - JavascriptBallpinView Answer on Stackoverflow
Solution 8 - JavascriptKamran AsgariView Answer on Stackoverflow
Solution 9 - JavascriptsickoView Answer on Stackoverflow