Merging object data

Say we have an application where we want to create a user when they signup. A user will look something like this, and consists of a number of properties, their name, their username, their phone number, their email and their password:

const user = {
  name: "",
  username: "",
  phoneNumber: "",
  email: "",
  password: "",
};

And let's say that when a user signs up, they don't need to provide all this data at once. This is standard for most applications--to make it easy for people to get through they just have to fill out a few necessary fields such as their username, email and password. We'll let users fill out these non-required fields like their phone number and name after they have created an account.

So let's say that a user fills out the form and gives us these required values. And we store them on an object called newUser:

const newUser = {
  username: "ReedBarger",
  email: "reed@gmail.com",
  password: "mypassword",
};

How do we use all of this data on the newUser object, but still use user as our model. Meaning, we want to merge these two objects, so we get all five properties that are needed for each user, including the empty values for name and phoneNumber.

To create a new object that preserves our default properties on the user object, while merging with new data, is possible with a new object method called Object.assign.

Object.assign() lets you update an object with properties from another object.

How Object.assign works

The first object that we pass to Object.assign is the object that we want to be returned. And and for any other arguments, those are objects that we want to merge into the first object; that is, we want to take their properties and put them on the first object. If properties of other objects have the same name as the first one, as is the case with username email and password, their values will be added to the original object.

console.log(Object.assign(user, newUser));

So based off of what we know about Object.assign, what do you think the result will be?

We get an object with the same properties names, the same keys as our original user object, but whose values are updated. Specifically the values for username, email and password. Why? Because newUser had those same properties as user, and since they were merged into user, the original values were overwritten.

However, there's a problem with Object.assign() as we're currently using it. We can see it if remove the console.log, but still execute Object.assign and then log the original user object afterwards.

Object.assign(user);

[Run code] What's happening is that Object.assign is mutating our original user object. From the first lesson in this section, when we talked about objects as reference types, we learned that the fact that objects are by passed by reference and not by value results in unexpected errors. And this is another consequence of the behavior of objects.

To fix this, we don't want to merge new values onto the original user object, but instead, use an entire new object. So we can create as the first argument to Object.assign, a brand new object to updated and then returned, and then merge in user and then newUser.

Object.assign({}, user, newUser);

So now let's console.log our new user data:

console.log(Object.assign({}, user, newUser));

[Run code] And we the created object has the correct values as before. But if we remove that log and console.log(user):

Object.assign({}, user, newUser);

So be aware that to avoid updating or mutating, as its called, our original data, pass an empty object as the object to be returned from Object.assign.

Benefit of Object.assign()

So this is a common problem that Object.assign helps us out with a lot when you have an object with values, but is missing some key-value pairs. So as a result, we need to fill in the remaining fields using a default object, in this case user.

So as you can see Object.assign can merge as many objects as we want into the first object. For example, say we want to add a verified property to each user that by default is false, to indicate that their email that they provided for signup is not verified yet:

How would we do that? Take a minute and try to merge such a property on the user object on your own...

We could create a new object, say one called verifiedDefault, or something like that, where the property verified is false:

const verifiedDefault = {
  verified: false,
};

And if we add it as a fourth argument--

console.log(Object.assign({}, user, newUser, verifiedDefault));

This will work. However, in this case, what might be a bit cleaner is to add the object inline without declaring it as a variable. Be aware that you can do so for small objects such as this one:

// const verifiedDefault = {
//   verified: false
// }

console.log(Object.assign({}, user, newUser, { verified: false }));

And this works exactly the same.

So to review, all that Object.assign is responsible for doing is gather object properties and putting it into a new object.

But as you can see, working with Object.assign is not very intuitive. It's weird to have to pass in that first empty object to avoid inadvertent mutations. And also, from a readability standpoint, it's not clear when we look at this line what's happening is that we're creating an object from other objects and properties. We don't exactly know what the .assign method does from first glance.

Object spread operator

Instead of using Object.assign, couldn't we just tell JavaScript that we want to take all the properties from an object and put in it a new one.

So let's start by creating a new variable called createdUser to store our final user data. And we'll just set this equal to an object:

const createdUser = {};

Now we want to tell JS that we want to just get all of the properties from the user object and then spread them into this new object.

const createdUser = { user };

And then after the user data, to put in the newUser data:

const createdUser = { user, newUser };

And the finally, instead of providing an object, we just put in the property that we need at the end: verified set to false:

const createdUser = { user, newUser, verified: false };

But with this syntax, we know that we're going to be creating nested objects on the properties user and newUser. So how can we just tell JS to spread all of the properties from an object into a new one?

In ES2018, such a feature arrived to do just that, called the object spread operator.

To spread in an object's properties into another one, we just have to include this syntax ... before the object. So we'll do that to both user and newUser. And if we console.log this...

const createdUser = { ...user, ...newUser, verified: false };
console.log(createdUser);

And we see that we get the same exact result as with Object.assign. Since that's the case, we know that the spread operator works in effectively the same way--it merges the properties that come later (after each comma) with the properties provided at the beginning. It does so as well in an immutable way.

So with the object spread operator, we have all the advantages of Object.assign() with reduced syntax and to create objects in a more intuitive way.

Order matters with Object.assign() and spread operator

And one final note is that the order matters significant for both Object.assign and the spread operator. If you add a value with the same key, it will use whatever value is declared last.

Say for example, we update our original user object to include the verified property. But here verified is set to true by default. If we console log our createdUser again, what will the final value of verified be on it?

const user = {
  name: "",
  username: "",
  phoneNumber: "",
  email: "",
  password: "",
  verified: true,
};
const createdUser = { ...user, ...newUser, verified: false };
console.log(createdUser);

We see that verified is false. That's because the last value a property is supplied is what the created object is provided with. So since verified was set to false at the end, that's why it ultimately had the value false.

As a rule, when performing updates to existing properties, make sure to provide update values at the end, or at least after the existing ones.

Review

In this video we covered both Object.assign and the object spread operator. They both do the same thing, but in general the object spread is much more straightforward to use since it's more evident that we are just creating a basic object. You tend to see it much for often that Object.assign and I would recommend you use the object spread the majority of time in your code. There's no significant advantage that Object.assign has over it.

And also, we covered both important use cases for Object.assign and the object spread:

  1. Using them to establish common defaults properties of any object through merging two or more objects. And
  2. To be able to non-destructively update or add properties. So we saw how to update the final object with the 'verified' property in a way that didn't mutate our original user objects.