Lodash isMatchWith Custom Validation on Missing Keys

Lodash's _.isMatchWith enables developers to perform deep comparisons between a target object and a source object using custom validation logic. When evaluating missing object keys, it provides a safe, flexible mechanism to distinguish between an explicitly undefined property value and a completely omitted key. This article details how _.isMatchWith invokes custom comparator functions, passes object contextual arguments, and enables secure assertion patterns to prevent false-positive matches on missing properties.

The Mechanics of _.isMatchWith

The _.isMatchWith(object, source, [customizer]) method iterates over the own enumerable string-keyed properties of the source object and determines if the target object contains equivalent property values. Unlike the standard _.isMatch, which uses Lodash's internal equality algorithm (a variant of SameValueZero), _.isMatchWith delegates individual comparisons to a user-defined customizer function.

For each property encountered on source, the customizer is invoked with five arguments:

customizer(objValue, srcValue, key, object, source)

If the customizer returns undefined, the method falls back to the default internal comparison. If it returns a boolean, that return value strictly determines whether the specific field matches.

The Challenge of Missing Keys and undefined

In JavaScript, accessing a non-existent property on an object evaluates to undefined. This creates a security and correctness risk when verifying schemas: a target object that completely lacks a key might inadvertently satisfy a rule if the source object matches on an undefined value or a permissive validator.

Standard equality functions cannot distinguish between:

  1. { role: undefined } (Key exists, explicitly set to undefined)
  2. {} (Key is missing entirely)

If validation logic relies solely on objValue === srcValue or merely checks the type of objValue, an omitted property might be treated as a valid undefined input, potentially bypassing required-field constraints.

Securely Validating Key Existence

To securely validate missing keys, the customizer function must leverage the full signature provided by _.isMatchWith—specifically using the fourth argument, object, to confirm property presence before validating the value.

By employing Object.prototype.hasOwnProperty.call(object, key) or the in operator, developers can assert whether the key physically exists on the target structure.

const _ = require('lodash');

function secureValidator(objValue, srcValue, key, object) {
  // Check if the property actually exists on the target object
  const hasKey = Object.prototype.hasOwnProperty.call(object, key);

  // If the target object is missing the key, explicitly fail validation
  if (!hasKey) {
    return false;
  }

  // If custom logic is required for validation (e.g., custom predicate)
  if (typeof srcValue === 'function') {
    return srcValue(objValue);
  }

  // Fall back to default Lodash comparison for existing keys
  return undefined;
}

const source = {
  requiredField: (val) => typeof val === 'string',
};

const incompleteTarget = {};
const validTarget = { requiredField: "configured" };

console.log(_.isMatchWith(incompleteTarget, source, secureValidator)); // false
console.log(_.isMatchWith(validTarget, source, secureValidator));       // true

Protection Against Prototype Pollution

When implementing custom validation against dynamic payloads, relying on Object.prototype.hasOwnProperty.call rather than invoking object.hasOwnProperty directly safeguards against prototype pollution vulnerabilities. If an untrusted payload contains a malicious hasOwnProperty override, the decoupled method call ensures reliable execution without throwing runtime exceptions or executing injected prototype methods.

By verifying the presence of the key on the parent object reference inside the customizer and explicitly returning false upon omission, _.isMatchWith prevents unintended fallback evaluations and enforces strict schema compliance.