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:

3. Enumeration Ordering

Modern ECMAScript engines order keys in for...in and _.toPairsIn iterations deterministically:

  1. Non-negative integer keys in ascending numerical order ("0", "1", ...).
  2. Other string keys (own properties) in chronological insertion order.
  3. 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:

[
  [
    "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.