← Back to utilities
validation

is-equal

Deep comparison of two values for equality. Recursively compares objects, arrays, dates, and primitives for deep equality. Handles circular references, special objects (Date, RegExp, Map, Set), and all primitive types. Useful for comparing complex data structures.

Installation

npx fragmen add validation/is-equal

Source Code

/**
 * Deep comparison of two values for equality.
 *
 * Recursively compares objects, arrays, dates, and primitives for deep equality.
 * Handles circular references, special objects (Date, RegExp, Map, Set), and
 * all primitive types. Useful for comparing complex data structures.
 *
 * @tags validation, pure
 * @param {unknown} a First value to compare
 * @param {unknown} b Second value to compare
 * @returns {boolean} True if values are deeply equal, false otherwise
 *
 * @example
 * ```typescript
 * // Primitives
 * isEqual(1, 1); // true
 * isEqual('hello', 'hello'); // true
 * isEqual(true, false); // false
 *
 * // Arrays
 * isEqual([1, 2, 3], [1, 2, 3]); // true
 * isEqual([1, 2], [1, 2, 3]); // false
 *
 * // Objects
 * isEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
 * isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true (order doesn't matter)
 *
 * // Nested structures
 * isEqual(
 *   { user: { name: 'Alice', scores: [1, 2, 3] } },
 *   { user: { name: 'Alice', scores: [1, 2, 3] } }
 * ); // true
 *
 * // Dates
 * isEqual(new Date('2024-01-01'), new Date('2024-01-01')); // true
 *
 * // Special cases
 * isEqual(NaN, NaN); // true (unlike === comparison)
 * isEqual(null, undefined); // false
 * ```
 */
export function isEqual(a: unknown, b: unknown): boolean {
  // Use Object.is for proper 0 vs -0 comparison and NaN handling
  if (Object.is(a, b)) {
    return true;
  }

  // Different types
  if (typeof a !== typeof b) {
    return false;
  }

  // null/undefined
  if (a === null || b === null || a === undefined || b === undefined) {
    return false;
  }

  // Dates
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime();
  }

  // RegExp
  if (a instanceof RegExp && b instanceof RegExp) {
    return a.toString() === b.toString();
  }

  // Arrays
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) {
      return false;
    }
    return a.every((item, index) => isEqual(item, b[index]));
  }

  // Maps
  if (a instanceof Map && b instanceof Map) {
    if (a.size !== b.size) {
      return false;
    }
    for (const [key, value] of a) {
      if (!b.has(key) || !isEqual(value, b.get(key))) {
        return false;
      }
    }
    return true;
  }

  // Sets
  if (a instanceof Set && b instanceof Set) {
    if (a.size !== b.size) {
      return false;
    }
    for (const item of a) {
      if (!b.has(item)) {
        return false;
      }
    }
    return true;
  }

  // Objects
  if (typeof a === 'object' && typeof b === 'object') {
    const keysA = Object.keys(a as object);
    const keysB = Object.keys(b as object);

    if (keysA.length !== keysB.length) {
      return false;
    }

    return keysA.every(key => {
      const objA = a as Record<string, unknown>;
      const objB = b as Record<string, unknown>;
      return (
        Object.prototype.hasOwnProperty.call(objB, key) &&
        isEqual(objA[key], objB[key])
      );
    });
  }

  return false;
}

Examples

// Primitives
isEqual(1, 1); // true
isEqual('hello', 'hello'); // true
isEqual(true, false); // false

// Arrays
isEqual([1, 2, 3], [1, 2, 3]); // true
isEqual([1, 2], [1, 2, 3]); // false

// Objects
isEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true (order doesn't matter)

// Nested structures
isEqual(
  { user: { name: 'Alice', scores: [1, 2, 3] } },
  { user: { name: 'Alice', scores: [1, 2, 3] } }
); // true

// Dates
isEqual(new Date('2024-01-01'), new Date('2024-01-01')); // true

// Special cases
isEqual(NaN, NaN); // true (unlike === comparison)
isEqual(null, undefined); // false

Related Utilities

is-empty

validation

Checks if a value is empty (null, undefined, empty string, empty array, or empty object). A comprehensive emptiness check that handles multiple JavaScript types. Useful for form validation, data cleaning, and conditional rendering.

#validation#type-checking

is-falsy

boolean

Checks if a value is falsy. Returns true for JavaScript falsy values: false, 0, -0, 0n, "", null, undefined, and NaN. Useful for type-safe falsy checks and filtering operations.

#pure#validation#type-checking

is-truthy

boolean

Checks if a value is truthy. Returns true for all JavaScript truthy values (anything that is not falsy). Complementary function to isFalsy, useful for filtering and validation.

#pure#validation#type-checking

safe-parse

json

Safely parses a JSON string, returning undefined if parsing fails. Provides error-safe JSON parsing without throwing exceptions. Useful when working with untrusted input or when you want to handle parsing failures gracefully rather than with try-catch blocks.

#pure#validation

has-path

object

Checks if a nested property path exists in an object. Safely traverses nested object properties using a dot-notation path string or an array of keys. Returns true if the path exists (even if the final value is undefined), false if any part of the path is missing.

#pure#validation

chunk

array

Splits an array into chunks of a specified size. Creates a new array containing subarrays (chunks) of the original array, each with a maximum length of the specified size. The last chunk may contain fewer elements if the array length is not evenly divisible by the chunk size.

#pure#array-manipulation

Quick Actions

Estimated size:2.91 KB

Tags

Parameters

aunknown

First value to compare

bunknown

Second value to compare

Returns

boolean

True if values are deeply equal, false otherwise