How Lodash toPairsIn Maps Prototyped Arrays
This article examines how the Lodash utility _.toPairsIn
processes multi-dimensional nested arrays that inherit enumerable
properties across a custom prototype chain. You will learn the exact
structural transformations that occur when indexed elements and
inherited prototype properties are converted into key-value pairs, how
JavaScript engine enumeration order affects the resulting
multi-dimensional mapping, and what happens to deeply nested structures
during this transformation.
The Underlying Mechanism
of _.toPairsIn
The _.toPairsIn method traverses an object and returns a
two-dimensional array of key-value pairs:
[[key1, value1], [key2, value2], ...]. Unlike
_.toPairs (which uses Object.keys under the
hood and only captures own enumerable properties),
_.toPairsIn iterates over both own and inherited enumerable
string-keyed properties using a standard for...in loop
mechanism.
Because JavaScript arrays are standard objects with integer-named
properties and a special length property,
_.toPairsIn treats arrays as general dictionaries. The
non-enumerable length property is omitted, while array
indices are cast to strings.
Multi-Dimensional Mapping on Standard vs. Prototyped Arrays
When applied to a standard multidimensional array:
const matrix = [[10, 20], [30, 40]];
_.toPairsIn(matrix);The outer array transforms into an explicit key-value matrix:
[
["0", [10, 20]],
["1", [30, 40]]
]_.toPairsIn is shallow. It maps the outer array into a
2-tuple matrix (Array<[string, any]>), leaving the
nested arrays intact as references unless _.toPairsIn is
explicitly invoked recursively.
When an array has a heavily customized prototype chain containing
enumerable properties, the mapping changes significantly. Consider a
prototype chain where Array.prototype or an intermediate
prototype object defines additional enumerable properties:
const proto = { inheritedMeta: "dataset-v1", inheritedCalc: () => {} };
const nestedRow = Object.assign(Object.create(proto), [1, 2]);
const heavilyPrototypedMatrix = Object.assign(Object.create(proto), [
nestedRow,
[3, 4]
]);Executing _.toPairsIn(heavilyPrototypedMatrix) produces
an expanded outer matrix:
[
["0", nestedRow],
["1", [3, 4]],
["inheritedMeta", "dataset-v1"],
["inheritedCalc", [Function: inheritedCalc]]
]Explicit Mapping Rules for Prototyped Nested Arrays
When catalogs are generated from these structures, the explicit mapping follows strict resolution rules:
1. Dimension Transformation
The mapping converts an \(N\)-element array with \(M\) enumerable prototype properties into an explicit \((N + M) \times 2\) matrix. The outer collection shifts from an indexed vector into an associative tuple list.
2. Index Stringification
All standard indices undergo string conversion:
- Index
0maps to tuple["0", value] - Index
1maps to tuple["1", value]
3. Enumeration Ordering
Modern ECMAScript engines order keys in for...in and
_.toPairsIn iterations deterministically:
- Non-negative integer keys in ascending numerical order
(
"0","1", ...). - Other string keys (own properties) in chronological insertion order.
- Enumerable string keys from each successive object along the prototype chain in order of discovery.
Consequently, array elements always populate the beginning of the mapped matrix, while prototype augmentations append to the end.
4. Recursive Mapping Behavior
If _.toPairsIn is mapped recursively down nested
dimensions, every level that inherits from the prototype mirrors this
expansion:
function deepToPairsIn(value) {
if (!_.isObject(value)) return value;
return _.toPairsIn(value).map(([key, val]) => [key, deepToPairsIn(val)]);
}Applied to a multi-dimensional array where both parent and child
nodes inherit proto:
- The root level yields
["0", ...],["1", ...], plus inherited properties. - The child level at index
"0"expands identically, mapping its internal elements followed by the inherited properties:
[
[
"0",
[
["0", 1],
["1", 2],
["inheritedMeta", "dataset-v1"],
["inheritedCalc", [Function: inheritedCalc]]
]
],
[
"1",
[
["0", 3],
["1", 4]
]
],
["inheritedMeta", "dataset-v1"],
["inheritedCalc", [Function: inheritedCalc]]
]5. Property Shadowing
If an own index or own named property shares an identical key name with an inherited prototype property, the own property completely shadows the prototype property. The prototype property will not produce a duplicate entry in the resulting tuple list.