Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

diffObjectsDeep

Recursively computes the deep difference between two objects. For each differing key, returns a tuple [aValue, bValue]. Supports nested objects and arrays.

TypeScript
Copied!
1/**
2 * Recursively computes the deep difference between two objects.
3 * For each differing key, returns a tuple [aValue, bValue].
4 * Supports nested objects and arrays.
5 *
6 * @param a - First object to compare.
7 * @param b - Second object to compare.
8 * @returns A new object with differences at each path.
9 */
10export function diffObjectsDeep(
11  a: Record<string, any>,
12  b: Record<string, any>
13): Record<string, any> {
14  const result: Record<string, any> = {};
15
16  const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
17
18  for (const key of keys) {
19    const valA = a[key];
20    const valB = b[key];
21
22    const bothAreObjects =
23      valA &&
24      valB &&
25      typeof valA === 'object' &&
26      typeof valB === 'object' &&
27      valA.constructor === valB.constructor;
28
29    if (bothAreObjects && !Array.isArray(valA)) {
30      const nestedDiff = diffObjectsDeep(valA, valB);
31      if (Object.keys(nestedDiff).length > 0) {
32        result[key] = nestedDiff;
33      }
34    } else if (Array.isArray(valA) && Array.isArray(valB)) {
35      if (valA.length !== valB.length || valA.some((v, i) => v !== valB[i])) {
36        result[key] = [valA, valB];
37      }
38    } else if (valA !== valB) {
39      result[key] = [valA, valB];
40    }
41  }
42
43  return result;
44}
  • Recursive Deep Comparison

    Traverses nested structures (including plain objects and arrays) to detect differences at any depth.

  • Detailed Change Representation

    Returns [aValue, bValue] tuples for differing leaf nodes, giving clear visibility into both sides of the change.

  • Handles Arrays and Objects Intelligently

    Differentiates between array and object comparison logic for more accurate diffing.

  • Minimal Output for Changed Paths

    Excludes identical values and returns only the paths where actual changes occurred, making the output compact and relevant.

  • Constructor-Aware Comparisons

    Avoids false positives by checking that compared values share the same constructor (e.g., not mixing objects with arrays or class instances).

Tests | Examples

TypeScript
Copied!
1test('diffObjectsDeep - top-level difference', () => {
2  const a = { name: 'Alice', age: 30 };
3  const b = { name: 'Bob', age: 30 };
4  expect(diffObjectsDeep(a, b)).toEqual({ name: ['Alice', 'Bob'] });
5});
6
7test('diffObjectsDeep - nested object difference', () => {
8  const a = { user: { id: 1, name: 'Alice' } };
9  const b = { user: { id: 2, name: 'Alice' } };
10  expect(diffObjectsDeep(a, b)).toEqual({ user: { id: [1, 2] } });
11});
12
13test('diffObjectsDeep - deep nesting', () => {
14  const a = { settings: { theme: { dark: true } } };
15  const b = { settings: { theme: { dark: false } } };
16  expect(diffObjectsDeep(a, b)).toEqual({
17    settings: { theme: { dark: [true, false] } },
18  });
19});
20
21test('diffObjectsDeep - array difference', () => {
22  const a = { tags: ['a', 'b'] };
23  const b = { tags: ['a', 'c'] };
24  expect(diffObjectsDeep(a, b)).toEqual({ tags: [['a', 'b'], ['a', 'c']] });
25});
26
27test('diffObjectsDeep - detects missing keys', () => {
28  const a = { name: 'Alice' };
29  const b = {};
30  expect(diffObjectsDeep(a, b)).toEqual({ name: ['Alice', undefined] });
31});
32
33test('diffObjectsDeep - equal deeply nested objects', () => {
34  const a = { config: { features: { x: true, y: false } } };
35  const b = { config: { features: { x: true, y: false } } };
36  expect(diffObjectsDeep(a, b)).toEqual({});
37});

Common Use Cases

  • Deep State Comparison

    Detect differences between two deeply nested state trees in React, Redux, or similar architectures.

  • Audit Logging and History Tracking

    Log or snapshot fine-grained changes in structured data (e.g., documents, user profiles, settings).

  • Form Change Detection

    Identify which specific fields have been altered across complex or nested form inputs.

  • Patch or Sync Generation

    Generate targeted update payloads for syncing with APIs or databases without resending unchanged data.

  • Testing and Assertions

    Compare expected vs. actual deeply nested structures in unit or integration tests to pinpoint mismatches.

Codebase: Utilities -> Objects -> diffObjectsDeep | Yevhen Klymentiev