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:

  1. state.user was created as an empty object.
  2. state.user.profile was created as an empty object.
  3. state.user.profile.loginCount evaluated to undefined.
  4. The callback was called as updater(undefined), returning 1.
  5. 1 was assigned to state.user.profile.loginCount.