This is a continuation of my previous post about Higher-order functions in Lodash. Most people know Lodash from constructs like this:

But Lodash is much more than a list manipulation library. In this post I would like to shed a light on some less popular, yet in my opinion, extremely useful Lodash features.

Disclaimer: This article assumes that the reader is familiar with concept of higher-order functions and knows how functions like _.curry() and _.partial() work. Moreover, in this article when I refer to “Lodash” I mean the lodash/fp variant of Lodash. If you haven’t heard of lodash/fp , higher-order functions or just need some refresh of your memory, please have a look at my previous article on Higher-order functions in Lodash.

One of the things I love in Lodash is that it is extremely flexible and adaptable. Even if it doesn’t have the specific function you need, there is a high chance that you can build one with just a few lines of code. Lodash’s authors placed extension points throughout the codebase that allow developers to further customize its behavior. One such extension point are Customizers.

Customizers

Object-Oriented programmers will recognize Customizers as the Strategy pattern from the famous Design Patterns: Elements of Reusable Object-Oriented Software book by The “Gang of Four”.

Customizers allow you to significantly change object behavior by substituting one strategy for another.

Let’s have a look at how customizers work in practice. Suppose we have a partial contact information, that we would like to combine into one object. As you might expect Lodash already provides a function that does the job for us. _.merge() function merges two objects, property by property:

However, if same property is present in both merged objects property value from last object wins. In our example it is unfortunate, as we loose information about one of the contact’s phone number.

Fortunately there is an alternative version of _.merge() that accepts an additional function which allows to customize a way in which properties are merged. This customization function is going to be invoked for every property (also nested properties) that should be merged (properties from the second merged object). Values of property to be merged will be passed as two first parameters. Let’s give it a try:

Bonus: Alternatively customizer can be defined this way: let customizer = _.cond([[_.isArray, _.concat]]);

If one of merged properties points to an array then our customizer returns a new array that will include values from both merged objects. Notice that if merged value is not an array our customizer won’t return any value (or to put it in another words — it will return undefined ). In such situation Lodash will fallback to default strategy (the one from _.merge() function).

But why should we limit ourselves just to arrays concatenation?

Here is how we can make our customizer even more generic:

In this new version of customizer, if second of the merged objects contains any functions then instead of assigning those functions to resulting object, we simply invoke them passing as parameters values from matching property from the first merged object.

Now we will fix this customizer to as a first parameter of _.mergeWith() . Let’s call this resulting function patch :

var patch = recipe => _.mergeWith(customizer, _, recipe);

Remember that all lodash/fp functions are auto-curried, so we can pass them subset of parameters, as well as parameter placeholders _ and in result we will get new function with some of the parameters fixed.

The resulting patch() is a higher-order function that returns new function that transforms objects based on provided recipe. Recipes are formulated in a pretty declarative way, by explicitly telling which function to use in order to merge given property. If property points to anything other than function, default strategy applies.

Advanced note: order of parameters in _.mergeWith(customizer, object, source) is a little bit unfortunate, as it accepts data ( object ) parameter as a second and not last parameter. If it would be the other way we could fully benefit from currying and define patch simply as:

var patch = _.mergeWith(customizer)

However, actual order of parameters forced us to skip second of its parameter using _ .

Alternatively, we could re-arrange parameters using _.rearg() like this:

var mergeRearg = _.rearg(_.mergeWith, [0, 2, 1]);

var patch = mergeRearg(customizer);

or just (using _.flip() ):

var patch = _.flip(_.mergeWith(customizer));