How Lodash defaultsDeep Handles Nested Missing Properties
In the Lodash JavaScript library, _.defaultsDeep
provides a robust way to fill in default values for complex, nested data
structures. This article explains how _.defaultsDeep
recursively traverses objects to populate missing nested properties
while strictly preserving existing values, how it differs from shallow
merging utilities, and how it treats edge cases like falsy values.
The Core Mechanism of
_.defaultsDeep
Unlike shallow assignment methods like Object.assign or
Lodash's standard _.defaults, which only evaluate top-level
keys, _.defaultsDeep traverses objects recursively. It
accepts a destination object followed by one or more source objects
containing default values, proceeding from left to right.
During traversal, the function inspects each nested property:
- Existence Check: It checks whether the property key exists on the destination object at the current depth.
undefinedEvaluation: If the property exists and its value is notundefined,_.defaultsDeepleaves the property untouched.- Recursive Descent: If both the destination property and the corresponding source property contain plain objects, the method steps into that sub-tree rather than performing a direct assignment.
- Insertion: If a key does not exist on the
destination object or resolves strictly to
undefined, the corresponding value from the source object is copied over.
Why Shallow Defaults Fail with Nested Objects
A standard shallow default method replaces entire object references rather than merging them. Consider an existing user configuration object:
const userConfig = {
theme: {
mode: 'dark'
}
};
const defaultConfig = {
theme: {
mode: 'light',
fontSize: 14
}
};Using a shallow method like
_.defaults(userConfig, defaultConfig) checks if
userConfig.theme exists. Because theme already
exists as an object, the entire default theme object is
ignored, leaving fontSize completely missing.
_.defaultsDeep(userConfig, defaultConfig) solves this by
navigating inside theme. It recognizes that
userConfig.theme.mode is already defined as
'dark' and leaves it alone, but sees that
userConfig.theme.fontSize is undefined. It
copies only fontSize: 14 into
userConfig.theme.
The resulting object becomes:
{
theme: {
mode: 'dark',
fontSize: 14
}
}Handling of Falsy Values and
null
A critical aspect of _.defaultsDeep is its strict
reliance on the undefined check to prevent accidental
overwrites:
- Falsy Values: Values such as
false,0,NaN, and empty strings""are considered explicit definitions. If a destination property holds0, a default value of100will not overwrite it. nullValues: In JavaScript,nullrepresents an intentional absence of an object value. Consequently,_.defaultsDeeptreatsnullas a defined value and will not overwrite it with the default value from a source object. Only explicitundefinedor entirely absent keys trigger the default assignment.
Mutability and Cloning
By default, _.defaultsDeep mutates the first object
passed to it. To avoid mutating the original data structure, pass an
empty object as the first argument:
const finalConfig = _.defaultsDeep({}, userConfig, defaultConfig);This pattern returns a new object containing the merged results while
leaving userConfig and defaultConfig
intact.