Skip to main content

Overview

The DiffUtils class provides utilities for deep comparison, change detection, and patch generation. Perfect for tracking object changes, implementing undo/redo, synchronizing data, and detecting differences in complex data structures.

Import

import { DiffUtils } from 'bytekit';

Usage

Deep Comparison

const obj1 = { name: 'John', age: 30, meta: { role: 'admin' } };
const obj2 = { name: 'John', age: 30, meta: { role: 'admin' } };
const obj3 = { name: 'John', age: 31, meta: { role: 'admin' } };

DiffUtils.deepEqual(obj1, obj2); // true
DiffUtils.deepEqual(obj1, obj3); // false

Detect Changes

const oldState = {
  name: 'John',
  age: 30,
  role: 'user'
};

const newState = {
  name: 'John',
  age: 31,
  role: 'admin',
  verified: true
};

const diff = DiffUtils.diff(oldState, newState);
console.log(diff);
// {
//   changed: ['age', 'role'],
//   added: ['verified'],
//   removed: []
// }

Generate and Apply Patches

const original = { count: 0, status: 'idle' };
const updated = { count: 5, status: 'active', timestamp: 1234567890 };

// Generate patches
const patches = DiffUtils.createPatch(original, updated);
console.log(patches);
// [
//   { op: 'replace', path: 'count', value: 5, oldValue: 0 },
//   { op: 'replace', path: 'status', value: 'active', oldValue: 'idle' },
//   { op: 'add', path: 'timestamp', value: 1234567890 }
// ]

// Apply patches
const result = DiffUtils.applyPatch(original, patches);
console.log(result);
// { count: 5, status: 'active', timestamp: 1234567890 }

Undo/Redo Implementation

const original = { count: 0 };
const updated = { count: 5 };

// Create forward patches
const patches = DiffUtils.createPatch(original, updated);

// Apply changes
let state = DiffUtils.applyPatch(original, patches);

// Undo - reverse patches
const undoPatches = DiffUtils.reversePatch(patches);
state = DiffUtils.applyPatch(state, undoPatches);
console.log(state); // { count: 0 }

Deep Diff with Nested Paths

const oldObj = {
  user: {
    profile: { name: 'John', age: 30 },
    settings: { theme: 'dark' }
  }
};

const newObj = {
  user: {
    profile: { name: 'John', age: 31 },
    settings: { theme: 'light', notifications: true }
  }
};

const deepDiff = DiffUtils.deepDiff(oldObj, newObj);
console.log(deepDiff);
// {
//   changed: ['user.profile.age', 'user.settings.theme'],
//   added: ['user.settings.notifications'],
//   removed: []
// }

Merge Objects

const obj1 = { name: 'John', age: 30, meta: { role: 'user' } };
const obj2 = { age: 31, meta: { verified: true }, email: 'john@example.com' };

// Deep merge
const merged = DiffUtils.merge(obj1, obj2);
console.log(merged);
// {
//   name: 'John',
//   age: 31,
//   meta: { role: 'user', verified: true },
//   email: 'john@example.com'
// }

// Use first object's values
const first = DiffUtils.merge(obj1, obj2, 'first');

// Use second object's values
const second = DiffUtils.merge(obj1, obj2, 'second');

API Reference

deepEqual

Deep compare two values for equality.
a
unknown
required
First value to compare
b
unknown
required
Second value to compare
return
boolean
Returns true if values are deeply equal
DiffUtils.deepEqual({ a: 1 }, { a: 1 }); // true
DiffUtils.deepEqual({ a: 1 }, { a: 2 }); // false

diff

Compare two objects and return changed, added, and removed keys.
oldObj
Record<string, unknown>
required
Original object
newObj
Record<string, unknown>
required
New object to compare
return
DiffResult
Object containing arrays of changed, added, and removed keys
interface DiffResult {
  changed: string[];
  added: string[];
  removed: string[];
}

deepDiff

Perform deep diff with nested paths using dot notation.
oldObj
unknown
required
Original object
newObj
unknown
required
New object to compare
prefix
string
default:"''"
Path prefix for nested keys
return
DiffResult
Object with nested paths (e.g., ‘user.profile.age’)

createPatch

Generate patches to transform one object into another.
oldObj
Record<string, unknown>
required
Original object
newObj
Record<string, unknown>
required
Target object
return
Patch[]
Array of patch operations
interface Patch {
  op: 'add' | 'remove' | 'replace';
  path: string;
  value?: unknown;
  oldValue?: unknown;
}

applyPatch

Apply patches to an object.
obj
Record<string, unknown>
required
Object to patch
patches
Patch[]
required
Array of patches to apply
return
Record<string, unknown>
New object with patches applied (does not mutate original)

reversePatch

Reverse patches to create undo operations.
patches
Patch[]
required
Patches to reverse
return
Patch[]
Reversed patches for undo
const patches = DiffUtils.createPatch(oldObj, newObj);
const undoPatches = DiffUtils.reversePatch(patches);

merge

Merge two objects with conflict resolution strategy.
obj1
Record<string, unknown>
required
First object
obj2
Record<string, unknown>
required
Second object
strategy
'first' | 'second' | 'merge'
default:"'merge'"
Conflict resolution strategy:
  • 'first': Use values from obj1
  • 'second': Use values from obj2
  • 'merge': Deep merge (default)
return
Record<string, unknown>
Merged object

getSummary

Get a human-readable summary of changes.
diff
DiffResult
required
Diff result from diff() or deepDiff()
return
string
Summary string (e.g., “2 changed, 1 added, 0 removed”)
const diff = DiffUtils.diff(oldObj, newObj);
const summary = DiffUtils.getSummary(diff);
console.log(summary); // "2 changed, 1 added"

Types

DiffResult

interface DiffResult {
  changed: string[];
  added: string[];
  removed: string[];
}

Patch

interface Patch {
  op: 'add' | 'remove' | 'replace';
  path: string;
  value?: unknown;
  oldValue?: unknown;
}

Use Cases

State Management

class StateManager {
  private history: Patch[][] = [];
  private state: any;
  
  update(newState: any) {
    const patches = DiffUtils.createPatch(this.state, newState);
    this.history.push(patches);
    this.state = newState;
  }
  
  undo() {
    const patches = this.history.pop();
    if (patches) {
      const undoPatches = DiffUtils.reversePatch(patches);
      this.state = DiffUtils.applyPatch(this.state, undoPatches);
    }
  }
}

Data Sync

const localData = await fetchLocal();
const serverData = await fetchServer();

const diff = DiffUtils.diff(localData, serverData);

if (diff.changed.length > 0 || diff.added.length > 0) {
  const patches = DiffUtils.createPatch(localData, serverData);
  await syncToServer(patches);
}

Change Tracking

const observer = {
  original: data,
  
  check(current: any) {
    const diff = DiffUtils.deepDiff(this.original, current);
    const summary = DiffUtils.getSummary(diff);
    
    if (diff.changed.length > 0) {
      console.log(`Changes detected: ${summary}`);
      console.log('Modified fields:', diff.changed);
    }
  }
};