How Lodash _.set Creates Missing Nested Structures
The _.set method in the Lodash JavaScript library
provides a safe, declarative mechanism for setting values at deeply
nested paths within objects and arrays. In native JavaScript, attempting
to assign a value to a non-existent nested property typically results in
a runtime TypeError when an intermediate property is
undefined or null. Lodash circumvents this
problem by parsing the target path, traversing the object tree, and
automatically instantiating intermediate plain objects or arrays
whenever a segment along the path does not exist.
The Problem with Native Nested Assignment
In standard JavaScript, dynamic deep assignment often requires defensive coding:
// Native error if user or profile is undefined
data.user.profile.settings.theme = 'dark'; // Throws TypeErrorWhile ECMAScript introduced optional chaining (?.) for
safe deep reads, it does not support safe deep writes. Developers
traditionally had to write repetitive boilerplate to ensure every level
of an object tree existed before assigning a value.
Step 1: Path Normalization
When you invoke _.set(object, path, value), Lodash first
normalizes the path argument using its internal
toPath utility. Paths can be provided as an array of keys
or as dot/bracket notation strings:
- Array notation:
['users', '0', 'name'] - String notation:
'users[0].name'or'users.0.name'
Lodash parses string paths into a unified array of string keys. This ensures that brackets and dots are treated consistently throughout the traversal process.
Step 2: Iterative Traversal and Validation
Once the path is resolved into an array of keys, Lodash iterates through the keys sequentially up to the second-to-last element. At each step, it evaluates the property on the current target object:
- Existence Check: It checks if the property corresponding to the current key already exists.
- Type Check: It verifies whether the existing value is an object or a function (a valid container). If the property holds a primitive value (like a string or boolean), it cannot hold nested properties, so Lodash treats it as needing replacement to fulfill the path.
Step 3: Determining Structure Type (Object vs. Array)
If an intermediate segment is missing or not a valid object,
_.set must instantiate a new structure. To decide whether
to create an array ([]) or an object ({}),
Lodash looks ahead to the next key in the normalized path:
- Array Creation: If the upcoming key is an integer,
an integer-like string (e.g.,
'0','1'), or an index within brackets, Lodash initializes the current property as an empty Array ([]). - Object Creation: If the upcoming key is non-numeric
(e.g.,
'profile','id'), Lodash initializes the current property as an empty Object ({}).
This lookahead behavior ensures that structures match developer expectations without requiring explicit schema definitions:
const obj = {};
// Next key after 'items' is '0', so 'items' becomes an Array
_.set(obj, 'items[0].name', 'Widget');
console.log(Array.isArray(obj.items)); // true
console.log(obj.items[0].name); // 'Widget'Step 4: Final Value Assignment
Once the iteration reaches the final key in the path, all
intermediate structures are guaranteed to exist as valid containers.
Lodash directly assigns the provided value to the final
property on the innermost reference and returns the mutated original
root object.
Through path tokenization, recursive traversal, index-based
lookahead, and automatic container generation, Lodash _.set
avoids runtime errors and simplifies deep object state updates.