How Lodash zipObjectDeep Handles Dynamic Paths
Lodash’s _.zipObjectDeep constructs deeply nested object
hierarchies from parallel arrays of property paths and values. When
these path strings are dynamically generated at runtime, the method
parses each path into discrete tokens and creates missing intermediary
containers by inspecting subsequent tokens. This article explains the
step-by-step mechanism Lodash uses to tokenize dynamic path strings,
infer whether intermediary nodes should be arrays or objects, and
assemble the final structure.
Path Tokenization and Resolution
Before creating any nested structures, _.zipObjectDeep
relies on Lodash’s internal path-parsing logic (similar to
_.toPath). Regardless of whether a path is hardcoded or
dynamically constructed using template literals or string concatenation
(such as `users[${index}].profile.${field}`), the string is
broken down into a flat list of property keys.
Lodash normalizes both dot notation (a.b.c) and bracket
notation (a[0].b or a['b']). During this
phase, dynamic characters and runtime expressions have already been
resolved by JavaScript into a standard string, allowing the parser to
treat dynamic paths identically to static ones.
Look-Ahead Container Inference
To build the intermediate nodes, the algorithm traverses the
tokenized path list from left to right. At each token, it checks whether
the current target object contains an existing reference at that key. If
the property is undefined, _.zipObjectDeep must choose
whether to instantiate a plain object ({}) or an array
([]).
It determines this container type by inspecting the next segment in the path:
- Numeric Index Look-Ahead: If the subsequent segment
is an integer (or a numeric string like
'0'), Lodash assumes array traversal and initializes the current property as an empty array ([]). - Key String Look-Ahead: If the subsequent segment is
non-numeric (such as a string key like
'name'or'details'), Lodash initializes the current property as an empty object ({}).
For example, given the dynamically produced path
users.0.name:
- Processing
users: Lodash checks the next token (0). Because it is a numeric index,usersis initialized as an array:users = []. - Processing
0: Lodash checks the next token (name). Because it is a non-numeric string, index0is initialized as an object:users[0] = {}. - Processing
name: This is the final segment, so it assigns the corresponding value without creating further containers.
Mutative Traversal
Once an intermediary node is instantiated (or located if it already exists from a preceding path), the internal pointer advances into that nested array or object. The assignment occurs mutatively on that reference, preserving existing sibling properties that may have been created earlier in the loop.
When paths contain sparse array indexes—for example, dynamically
setting index 3 before index 0—JavaScript
arrays will allocate the intermediate elements as empty slots
(undefined), retaining standard array indexing
behavior.
const dynamicIndex = 2;
const dynamicField = 'status';
const paths = [
`items[${dynamicIndex}].${dynamicField}`,
`items[${dynamicIndex}].meta.id`
];
const values = ['active', 101];
const result = _.zipObjectDeep(paths, values);In this execution:
itemsis initialized as an array because the next segment is index2.- Indexes
0and1remain empty slots, whileitems[2]is initialized as an object because the subsequent segment is'status'. 'status'receives the value'active'.- On the second path,
items[2]already exists, so Lodash reuses it, creates the intermediatemetaobject, and setsidto101.