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:

  1. 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 ([]).
  2. 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:

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:

  1. items is initialized as an array because the next segment is index 2.
  2. Indexes 0 and 1 remain empty slots, while items[2] is initialized as an object because the subsequent segment is 'status'.
  3. 'status' receives the value 'active'.
  4. On the second path, items[2] already exists, so Lodash reuses it, creates the intermediate meta object, and sets id to 101.