1. 2
    Deep Merge Objects in JavaScript with Spread, Lodash, and Deepmerge
    4m 22s

Deep Merge Objects in JavaScript with Spread, Lodash, and Deepmerge

Chris Achard
InstructorChris Achard
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 3 years ago

If you are merging two objects that contain other objects or arrays, then you probably want to deeply merge those objects, instead of just shallow merging them. In this lesson, we'll look at three different ways to deeply merge objects, depending on what you want to accomplish: using the spread operator, using lodash's merge function, or using the deepmerge npm library.

Instructor: [00:00] We have a person object here that has a name object with a first name of Joe. It says he's 23 years old. It gives Joe's favorite color as green.

[00:11] At some point later, we get an update that we want to apply to the Joe person object, where we want to add the last name of Smith to the name object. We want to overwrite his favorite color with blue instead of green. We want the favorite book entry to be "Harry Potter."

[00:27] If we just went and merged these two together with the spread operator, then what we'd get is a shallow merge. The entire name object from update would just overwrite the name object from the original person, which clears out the first name key and data.

[00:42] The rest of the data we got merged OK though. What we need to do now is a deep merge of the two objects so that this name object is properly merged as well. To do that, we'll extend the spread.

[00:53] Since this is just an object that we're making, we can exactly specify the keys we want and how we want them to be handled. We'll keep the person and update spreads. We'll add a new key for name and spread the name object from the person into that, followed by the name object from the update.

[01:11] The result of that is a new merged object. Inside of that is a new name object with the keys and values from both person and from update. However, this is only two levels deep. It already seems a bit complicated. Let's bring in a library to help us out.

[01:28] In a terminal, I'll yarn add lodash. Back in the file, we can require lodash and then use lodash's merge function, which will handle the deep merge for us. We can say that the merged value is the merge of person and update. Be careful here. If we left it like this, lodash would mutate the person object by merging the update object into it.

[01:52] Instead, just like if we were using Object.assign, we're going to make a brand-new object first and let lodash first merge the person object into that new object and then merge the update object into that object.

[02:05] Now, we have a successfully deeply merged object in one line of code. There's one more case that we're going to worry about though, which is arrays. Let's say our person starts with an array of pets that contains a dog and a lizard.

[02:19] Later, our update contains an array of pets that just says, "Cat." Does that update mean that Joe replaced his dog and lizard with just a cat, or does it mean that he added a cat to his dog and lizard for a total of three pets?

[02:32] With lodash's merge, you don't have a choice. If we run this now, it doesn't actually do either of those things. What it does instead is it treats the arrays like an object, since arrays in JavaScript are actually objects.

[02:45] It first applies the person pets array, which has an element at array index zero and an element at array index one. Then it merges in the pets array from the update, which only has an element at array index zero. It overwrites dog with cat. That's the final answer.

[03:01] That might be what you want to do, but it's not for our case here. We could switch this back to using the deep spread, since the spread operator also works on arrays. With that, we get a pets array that contains all three animals.

[03:16] Again though, we moved away from spread the first time because it was getting complex. It just got bigger now. Let's reach for another library, called deepmerge. We'll yarn add deepmerge and require that.

[03:32] Now, we can deepmerge the person with the update. Deepmerge knows about the array problem. We can pass an option called array merge. That's a function. We can tell deepmerge exactly what we want to do with the arrays.

[03:45] We can give it a function with the destination array and the source array as arguments. We want to return a new array. We'll use spread here to say that we want to concatenate the two arrays together.

[03:56] Now when we run that, it works. We get a deeply merged object with the pet array handled like we want. When merging objects, the spread operator works great. It's fully customizable, but it can get tedious if the objects are very deeply nested or of an unknown structure.

[04:12] Lodash's merge works, but only if we want to treat arrays like objects. Deepmerge works. We can provide a custom array-handling function.

Jonathan Farber
Jonathan Farber
~ 4 years ago

Thank you for that clarification regarding merge methods, I was wondering, if lodash only deep merges at the depth of one level, would the array flat() or even flatMap() methods help in achieving the same as deepmerge package? and secondly, if I would want to merge/copy only implicit properties from a given large object, how would I perform that?

Chris Achard
Chris Achardinstructor
~ 4 years ago

The real problem I was trying to show with lodash is that it treads arrays as objects, so you can't control how they get merged. I think that means that using flat or flatmap won't fix the issue with lodash (unless you had something specific in mind - do you have a code example that might work?)

For pulling out only some properties from each, the spread operator is probably your best bet - you can pull just the values you want; or you could do something like a deepmerge into a new object, and then make a second new object that just pulls out the keys from that new object that you want in your final object.

Jonathan Farber
Jonathan Farber
~ 4 years ago

Hi Chris, sorry, your right I didn't explain myself right, let me be a little more precise - For example: when receiving data from an API endpoint as a JSON Object with nested arrays. for instance, weather current conditions endpoint, so in this case, in order to parse only the relevant properties of the data object (i.e, not the entire object, let say only the temperature field in this case).

for illustration, I'll do my best to mock this out (don't judge me too hard, im still green behind the ears 🤓)

"Data": [
  {
    "DateTime": {
	  "DateString": "2020-05-11T20:50:40.356Z",
	  "WeatherText": "Clear"
	  "WeatherIcon": 6
	},
	"HasPrecipitation": false,
	"PrecipitationType": null,
	"Temperature": {
      "Metric": {
        "Value": 20,
        "Unit": "C"
      },
      "Imperial": {
        "Value": 68,
        "Unit": "F"
      }
  	}
  }
 ]

so given this response, what i would do to resolve this would be, to create a function that checks:

  • if the data is an array or object.
  • then feed the data to the parser function in order to parse only relevant data property fields.

for example something like this:

const isArrayOrObject = (jsonData) => {
  let result = jsonData;
  if (Array.isArray(result.data)) {
    return result.data.map((result) => currentConditionsReducer(result));
  } else {
    return currentConditionsReducer(result.data);
  }
};
 
 export const currentConditionsReducer = (result, language) => {
  return {
    currentDate: dateFromString(result.DateString, language),
    weatherPhrase: result.WeatherText,
    icon: iconUrlResolver(result.WeatherIcon),
    temperature: result.Temperature.Imperial.Value,
	};
};

const dateFromString = (value, language) => {
  return new Date(value).toLocaleDateString(language, {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
  });

now back to my first question from earlier, i feel like there must be a better way to perform this then hard coding the values i am interested in parsing?

thank you for your patience so far... I hope i wasn't too hard to understand 😅

Jonathan Farber
Jonathan Farber
~ 4 years ago

Sorry my indentations moved due to copy/paste

Chris Achard
Chris Achardinstructor
~ 4 years ago

Hm - yeah, when data gets complex like that (especially when you have to check if it's an array or object, etc), then yeah, I'm not sure there's a better way then just doing it by hand.

The code you have there looks straightforward enough - not sure I could get it much shorter / simpler. So I think you've done about what I would do there :)

Jonathan Farber
Jonathan Farber
~ 4 years ago

Thank you, wow... i truly thought that I was going about this the wrong way, I don't know if you are familiar with Object Proxy methods like Object.fromEntries but i strongly think that there is a possibility to create a dynamic function which can map over any given data and merge the values, where I could use a single config enumeration to filter the relevant properties.

anyways, thank you so much for the input much appreciated, and if i find a better approach I'll be sure to update.

Markdown supported.
Become a member to join the discussionEnroll Today