Lodash _.update with Undefined Nested Properties
This article explains how the Lodash _.update method
processes function mapping when a targeted nested property resolves to
undefined. It details how intermediate path segments are
instantiated, how the leaf value is extracted and passed to the updater
callback, and how the resulting value is assigned back into the target
object.
Intermediate Path Traversal and Auto-Creation
When calling _.update(object, path, updater), Lodash
first parses the provided path (as a string like
'a.b.c' or an array like ['a', 'b', 'c']). It
traverses the structure from left to right.
If any intermediate segment in the path does not exist, or is
currently null or undefined, Lodash
automatically creates a container for it. By default, it initializes
missing segments as empty plain objects ({}). If a segment
key is an integer index (or string representation of an integer), Lodash
instantiates an empty array ([]) instead. This ensures
traversal does not throw a TypeError due to accessing
properties on undefined.
Leaf Resolution and the Updater Invocation
Once Lodash reaches the final segment of the path, it retrieves the
current value of that property. If the property does not exist on the
target parent, or if it explicitly holds the value
undefined, the retrieved value evaluates to
undefined.
Lodash then executes the mapping function (updater),
passing this resolved value directly as its sole argument:
updater(undefined);The function is executed regardless of whether the property
previously existed. It is up to the updater function to handle receiving
undefined, typically by leveraging JavaScript default
parameters, nullish coalescing, or explicit conditional checks.
Mutation and Assignment
Whatever value the updater function returns is assigned
directly to the target leaf property using an internal set
operation. This process mutates the original object in place and then
returns a reference to the mutated object.
Consider the following example:
const _ = require('lodash');
const state = {};
// The path 'user.profile.loginCount' does not exist
_.update(state, 'user.profile.loginCount', (count) => {
// 'count' is strictly evaluated as undefined
if (count === undefined) {
return 1;
}
return count + 1;
});
console.log(state);
// Output: { user: { profile: { loginCount: 1 } } }In this execution:
state.userwas created as an empty object.state.user.profilewas created as an empty object.state.user.profile.loginCountevaluated toundefined.- The callback was called as
updater(undefined), returning1. 1was assigned tostate.user.profile.loginCount.