Small, fast, zero dependency deep object and array comparison
npx @tessl/cli install tessl/npm-microdiff@1.5.0Microdiff is a tiny (<1kb), fast, zero dependency object and array comparison library. It provides a single diff() function that computes differences between JavaScript objects and arrays with full TypeScript support. The library is significantly faster than most other deep comparison libraries and handles cyclical references, special object types like Date and RegExp, and NaN values correctly.
npm install microdiffimport diff from "microdiff";For CommonJS:
const diff = require("microdiff").default;Named imports for TypeScript:
import diff, {
type Difference,
type DifferenceCreate,
type DifferenceRemove,
type DifferenceChange
} from "microdiff";import diff from "microdiff";
const obj1 = {
originalProperty: true,
nested: { value: 42 }
};
const obj2 = {
originalProperty: true,
newProperty: "new",
nested: { value: 100 }
};
const differences = diff(obj1, obj2);
console.log(differences);
// [
// { type: "CREATE", path: ["newProperty"], value: "new" },
// { type: "CHANGE", path: ["nested", "value"], value: 100, oldValue: 42 }
// ]The core diff function performs deep comparison between two objects or arrays and returns an array of difference objects describing all changes.
/**
* Deep comparison function that computes differences between two objects or arrays
* @param obj - First object/array to compare
* @param newObj - Second object/array to compare
* @param options - Optional configuration object (defaults to { cyclesFix: true })
* @returns Array of Difference objects describing changes
*/
function diff(
obj: Record<string, any> | any[],
newObj: Record<string, any> | any[],
options?: Partial<{ cyclesFix: boolean }>
): Difference[];Usage Examples:
import diff from "microdiff";
// Basic object comparison
const result1 = diff(
{ a: 1, b: 2 },
{ a: 1, b: 3, c: 4 }
);
// [
// { type: "CHANGE", path: ["b"], value: 3, oldValue: 2 },
// { type: "CREATE", path: ["c"], value: 4 }
// ]
// Array comparison
const result2 = diff([1, 2, 3], [1, 3, 4]);
// [
// { type: "CHANGE", path: [1], value: 3, oldValue: 2 },
// { type: "CHANGE", path: [2], value: 4, oldValue: 3 }
// ]
// Nested objects
const result3 = diff(
{ user: { name: "Alice", age: 25 } },
{ user: { name: "Alice", age: 26, active: true } }
);
// [
// { type: "CHANGE", path: ["user", "age"], value: 26, oldValue: 25 },
// { type: "CREATE", path: ["user", "active"], value: true }
// ]
// Disable cycle detection for performance
const result4 = diff(obj1, obj2, { cyclesFix: false });By default, microdiff detects and handles cyclical references to prevent infinite loops during comparison.
// Cyclical references are handled automatically
const obj1 = { a: {} };
obj1.a.parent = obj1;
const obj2 = { a: {} };
obj2.a.parent = obj2;
const result = diff(obj1, obj2); // Returns [] - objects are equivalentMicrodiff correctly handles special JavaScript object types by comparing their values rather than their internal structure.
// Date objects
const result1 = diff(
{ created: new Date('2023-01-01') },
{ created: new Date('2023-01-02') }
);
// [{ type: "CHANGE", path: ["created"], value: Date('2023-01-02'), oldValue: Date('2023-01-01') }]
// RegExp objects
const result2 = diff(
{ pattern: /abc/g },
{ pattern: /def/i }
);
// [{ type: "CHANGE", path: ["pattern"], value: /def/i, oldValue: /abc/g }]/** Union type representing all possible difference operations */
type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange;
/** Represents a CREATE operation (new property added) */
interface DifferenceCreate {
type: "CREATE";
/** Path to the created property as an array of keys */
path: (string | number)[];
/** Value of the created property */
value: any;
}
/** Represents a REMOVE operation (property deleted) */
interface DifferenceRemove {
type: "REMOVE";
/** Path to the removed property as an array of keys */
path: (string | number)[];
/** Previous value of the removed property */
oldValue: any;
}
/** Represents a CHANGE operation (property value modified) */
interface DifferenceChange {
type: "CHANGE";
/** Path to the changed property as an array of keys */
path: (string | number)[];
/** New value of the changed property */
value: any;
/** Previous value of the changed property */
oldValue: any;
}The path property in difference objects provides a hierarchical path to the changed property:
["user", "name"])[0, "property"])["users", 1, "profile", "email"])// Path examples
const obj1 = {
users: [
{ name: "Alice", profile: { email: "alice@old.com" } }
]
};
const obj2 = {
users: [
{ name: "Alice", profile: { email: "alice@new.com" } }
]
};
const result = diff(obj1, obj2);
// [{
// type: "CHANGE",
// path: ["users", 0, "profile", "email"],
// value: "alice@new.com",
// oldValue: "alice@old.com"
// }]Microdiff treats NaN values as equivalent during comparison, which differs from JavaScript's default behavior:
const result = diff({ value: NaN }, { value: NaN });
// Returns [] - NaN values are considered equalFor objects without cyclical references (like parsed JSON), disable cycle detection for better performance:
// ~49% faster when cycles detection is disabled
const result = diff(jsonObj1, jsonObj2, { cyclesFix: false });Microdiff runs on all modern JavaScript environments:
import diff from "https://deno.land/x/microdiff@1.5.0/index.ts"