Lodash toPlainObject: Flatten Inherited Properties

Lodash’s _.toPlainObject converts any input value into a plain JavaScript object by flattening its inherited prototype chain into direct, own enumerable properties. While standard JavaScript operations like Object.assign() or object spread ({ ...obj }) copy only an object's own properties, _.toPlainObject traverses enumerable properties across the entire inheritance hierarchy and assigns them as own properties of a newly created plain object.

The Mechanism of _.toPlainObject

In JavaScript, objects created via classes or constructor functions inherit properties and methods through the prototype chain. These inherited members do not reside directly on the instance, meaning methods like Object.prototype.hasOwnProperty() return false for them.

When _.toPlainObject(value) is executed, Lodash performs the following sequence:

  1. Creates a Clean Target: Lodash initializes a new, plain object literal ({}) without carrying over the prototype of the source instance.
  2. Traverses Enumerable Properties: Unlike Object.keys(), which only reads own properties, Lodash scans both own and inherited enumerable properties. Internally, this operates similarly to a for...in loop.
  3. Flattens Values to Own Properties: Each enumerable key discovered along the prototype chain is assigned directly onto the new target object as an own property.
  4. Stops at the Base Prototype: The traversal halts when reaching standard built-in prototype layers, such as Object.prototype, avoiding internal native properties.

Code Demonstration

Consider a scenario where a subclass inherits properties from a parent constructor:

const _ = require('lodash');

function Parent() {
  this.parentOwn = 'parent value';
}
Parent.prototype.inheritedFromParent = 'inherited parent value';

function Child() {
  Parent.call(this);
  this.childOwn = 'child value';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.inheritedFromChild = 'inherited child value';

const instance = new Child();

// Standard JavaScript spread only captures own properties
const spreadObject = { ...instance };
console.log(spreadObject.hasOwnProperty('inheritedFromChild')); // false

// Lodash toPlainObject flattens inherited properties
const plainObject = _.toPlainObject(instance);
console.log(plainObject.hasOwnProperty('inheritedFromChild'));  // true
console.log(plainObject.hasOwnProperty('inheritedFromParent')); // true
console.log(plainObject);
// Output:
// {
//   parentOwn: 'parent value',
//   childOwn: 'child value',
//   inheritedFromChild: 'inherited child value',
//   inheritedFromParent: 'inherited parent value'
// }

Key Differences and Limitations