How Lodash _.set Parses Bracket and Dot Notation
This article explains how Lodash's _.set method
internally parses string paths containing bracket and dot notations into
object keys. When manipulating nested data structures, Lodash must
accurately distinguish between property names separated by periods and
indices or property names enclosed in brackets. Below is an examination
of the internal path-casting pipeline, the regular expression engine
Lodash uses, and how both syntax styles are normalized into a unified
array of keys.
The
Normalization Pipeline: castPath and
stringToPath
When _.set(object, path, value) is called, the library
first ensures the path argument is converted into an array
of individual keys. This is handled by an internal utility function
called castPath.
- If the provided path is already an array,
castPathreturns it directly. - If the path is a symbol or a key that already exists directly on the target object, it is treated as a single literal key.
- Otherwise, the string is passed to
stringToPath, a memoized utility responsible for converting path strings into arrays of property names.
By converting all representations into an array early,
_.set avoids having to determine whether an access
operation used dots or brackets during the actual object traversal and
assignment steps.
The Unified Parsing
Engine: rePropName
Rather than using separate parsers or conditional logic branches to
detect dot notation versus bracket notation, Lodash relies on a single,
comprehensive regular expression named rePropName.
In the Lodash source code, rePropName is structured to
match valid path tokens sequentially. It matches three distinct
syntactic forms:
const rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;This pattern matches tokens using an alternation (|)
strategy:
1. Dot Notation Segments
([^.[\]]+)
This branch matches any sequence of characters that does not contain
a dot or opening/closing brackets. In a path like a.b.c, it
sequentially matches a, b, and c.
The dots act merely as delimiters excluded by the character class.
2. Bracket Notation Segments
(\[(?:... )\])
This branch captures keys enclosed in brackets and differentiates the content inside:
- Unquoted numbers or keys: Matches numeric indices
like
[0]or negative numbers like[-1]using(-?\d+(?:\.\d+)?). - Quoted strings: Matches single or double-quoted
keys, such as
['prop']or["complex-key"], using(["'])((?:(?!\2)[^\\]|\\.)*?)\2. It correctly handles escaped quote characters inside string properties.
When this branch matches, the capture groups isolate the inner content, stripping away the surrounding brackets and quotation marks.
3. Empty Tokens and Consecutive Delimiters
The final branch uses a lookahead
((?=(?:\.|\[\])(?:\.|\[\]|$))) to capture empty string keys
resulting from consecutive dots (e.g., a..b) or empty
brackets (e.g., a[]).
Constructing the Key Array
The stringToPath function executes
rePropName across the input string using
String.prototype.replace:
const result = [];
if (string.charCodeAt(0) === 46 /* . */) {
result.push('');
}
string.replace(rePropName, (match, expression, quote, subString) => {
let key = match;
if (quote) {
key = subString.replace(reEscapeChar, '$1');
} else if (expression) {
key = expression.trim();
}
result.push(key);
});
return result;During this execution:
- If a token was matched via quotes, Lodash extracts the inner string and unescapes any backslash-escaped characters.
- If a token was matched as an unquoted bracket expression, whitespace is trimmed, leaving the raw index or identifier.
- Standard dot-delimited tokens are pushed as-is.
For example, whether the input is 'user.posts[0].title'
or 'user["posts"]["0"].title', the resulting array produced
by stringToPath is identical:
['user', 'posts', '0', 'title']Traversal and Type Determination
Once stringToPath produces the key array,
_.set iterates through the segments using
baseSet. At each step:
- It looks ahead to the next key in the array.
- If the current target object lacks the required intermediate
property, Lodash checks if the next key is an integer index
(such as
'0'). - If the next key is an index, it initializes the missing segment as
an
Array; otherwise, it initializes it as anObject.
Lodash does not rely on bracket notation itself to decide whether to create an Array or an Object; instead, the bracket syntax is purely a string-tokenization mechanism that resolves to the same normalized key representation as dot notation.