How Lodash defaultsDeep Handles Undefined Values

This article examines how the _.defaultsDeep function in the Lodash JavaScript library handles properties explicitly set to undefined within a destination object. It details the underlying evaluation mechanism, demonstrates the behavior through code examples, and explains the functional difference between undefined and other falsy values when applying recursive default settings.

In Lodash, _.defaultsDeep recursively assigns enumerable string-keyed properties of source objects to the destination object. When evaluating whether to assign a property from a source object, _.defaultsDeep checks whether the corresponding property on the destination object resolves to undefined.

If a destination property is explicitly set to undefined (for example, { a: undefined }), _.defaultsDeep treats it as if the property does not exist or has not been populated. Consequently, the function overwrites the undefined value with the matching value from the source object.

Consider the following example:

const _ = require('lodash');

const destination = {
  user: {
    name: undefined,
    role: 'editor'
  }
};

const source = {
  user: {
    name: 'Anonymous',
    role: 'subscriber',
    theme: 'dark'
  }
};

const result = _.defaultsDeep(destination, source);

console.log(result);
// Output:
// {
//   user: {
//     name: 'Anonymous',
//     role: 'editor',
//     theme: 'dark'
//   }
// }

In this scenario, destination.user.name is explicitly set to undefined. Because the property resolves to undefined, _.defaultsDeep applies the source value 'Anonymous' to the destination. Conversely, destination.user.role is already defined as 'editor', so the source value 'subscriber' is ignored.

This behavior applies at every level of nested objects. If a nested object in the destination contains explicit undefined properties, _.defaultsDeep continues its traversal and replaces those values with the source properties at the identical path.

It is important to distinguish undefined from other falsy values in JavaScript, such as null, false, 0, or empty strings (""). The _.defaultsDeep function strictly checks for undefined. If a destination property is set to null or any other falsy value, it is considered defined, and the source object will not overwrite it:

const destinationWithNull = {
  user: {
    name: null
  }
};

const sourceFallback = {
  user: {
    name: 'Anonymous'
  }
};

const nullResult = _.defaultsDeep(destinationWithNull, sourceFallback);

console.log(nullResult);
// Output:
// {
//   user: {
//     name: null
//   }
// }

Unlike native operations such as Object.assign() or the object spread operator (...), which copy explicit undefined values directly into the target, _.defaultsDeep views undefined as a trigger to pull fallback values from the source.