Prototype Keys Preserved by Lodash toPlainObject
This article examines how the Lodash utility function
_.toPlainObject handles inherited properties across an
object's prototype chain. It clarifies the specific criteria inherited
keys must meet to be preserved during conversion, which properties are
deliberately discarded, and how this affects object flattening in
JavaScript applications.
The Criteria for Preserved Inherited Keys
When converting a custom class instance or an object with a prototype
chain into a plain object using _.toPlainObject, Lodash
does not retain the original prototype linkage. Instead, it flattens the
hierarchy by copying inherited properties directly onto the new object
as own properties.
To be successfully preserved and copied into the target plain object, an inherited property must meet two specific conditions:
- It must be enumerable: The property descriptor must
have
enumerable: true. - It must be string-keyed: The key must be a string or a primitive that coerces into a string.
Lodash achieves this internally by resolving keys via its
keysIn mechanism, which acts essentially like a
for...in loop. Any property defined up the prototype chain
that satisfies both conditions is extracted and assigned as an own,
enumerable property on the resulting plain object.
Inherited Keys That Are Excluded
Understanding what _.toPlainObject omits is equally
critical for predicting the shape of the output:
- Non-Enumerable Properties: Methods and properties
defined with
enumerable: falseare excluded. Standard prototype methods found on built-in prototypes likeObject.prototype.toString,Object.prototype.valueOf, or custom methods added viaObject.definePropertywith default settings will not be copied. - ES6 Class Methods: In ECMAScript 2015 (ES6) classes, methods defined inside class bodies are non-enumerable by default. Consequently, inherited class methods will not be transferred to the plain object.
- Symbol-Keyed Properties: Inherited properties keyed
by ES6
Symbolprimitives—such asSymbol.iteratoror custom symbols—are ignored, regardless of whether they are enumerable.
Practical Example
Consider an object created through constructor functions where prototypes are configured with different visibility:
function Parent() {}
Parent.prototype.inheritedStringKey = 'preserved'; // Enumerable: true
Object.defineProperty(Parent.prototype, 'inheritedNonEnumerable', {
value: 'dropped',
enumerable: false
});
Parent.prototype[Symbol('inheritedSymbol')] = 'dropped';
function Child() {
this.ownKey = 'preserved';
}
Child.prototype = Object.create(Parent.prototype);
const instance = new Child();
const result = _.toPlainObject(instance);In this scenario:
result.ownKeyevaluates to'preserved'.result.inheritedStringKeyevaluates to'preserved'as an own property ofresult.result.inheritedNonEnumerableisundefined.- The symbol key is omitted entirely.
In summary, _.toPlainObject exclusively preserves
inherited keys that are enumerable string properties, converting them
from inherited prototype references into direct, own properties on a
clean object instance.