diff
The diff utility compares two objects and returns an object containing only the properties that were changed or added in the second object. This is ideal for change tracking, auditing, and generating minimal data patches.
Source Code
View Source Code
ts
import type { Obj } from '../types';
import { isEqual } from '../typed/isEqual';
import { isObject } from '../typed/isObject';
/** Sentinel value returned by `diff` when a key exists in `prev` but not in `curr`. */
export const DELETED: unique symbol = Symbol('deleted');
export type DiffResult<T extends Obj> = { [K in keyof T]?: T[K] | typeof DELETED };
/**
* Computes the difference between two objects.
*
* Keys present in `prev` but absent in `curr` are marked with the `DELETED` sentinel.
*
* @example
* ```ts
* import { diff, DELETED } from '@vielzeug/toolkit';
*
* diff({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 }); // { c: DELETED }
* diff({ a: 1, b: 99 }, { a: 1, b: 2 }); // { b: 99 }
* ```
*
* @param curr - The current object.
* @param prev - The previous object.
* @param [compareFn] - A custom function to compare values.
* @returns An object containing new/modified/deleted properties.
*/
export function diff<T extends Obj>(
curr?: T,
prev?: T,
compareFn: (a: unknown, b: unknown) => boolean = isEqual,
): DiffResult<T> {
if (!curr && !prev) return {};
const result: Record<string, unknown> = {};
for (const key of new Set([...Object.keys(curr ?? {}), ...Object.keys(prev ?? {})])) {
const _curr = curr?.[key];
const _prev = prev?.[key];
if (isObject(_curr) && isObject(_prev)) {
const nestedDiff = diff(_curr as Obj, _prev as Obj, compareFn);
if (Object.keys(nestedDiff).length > 0) {
result[key] = nestedDiff;
}
} else if (!compareFn(_curr, _prev)) {
const wasDeleted = prev != null && key in prev && (curr == null || !(key in curr));
result[key] = wasDeleted ? DELETED : _curr;
}
}
return result as DiffResult<T>;
}Features
- Isomorphic: Works in both Browser and Node.js.
- Minimal Output: Only returns what has actually changed.
- Deep Comparison: Correctly identifies differences even in nested objects.
- Type-safe: Preserves typing for partially changed objects.
API
ts
function diff<T extends object, U extends object>(a: T, b: U): Partial<T & U>;Parameters
a: The base (original) object.b: The target (updated) object to compare against the base.
Returns
- A new object representing the delta between
aandb.
Examples
Basic Diff
ts
import { diff } from '@vielzeug/toolkit';
const original = { id: 1, status: 'pending', tags: ['new'] };
const updated = { id: 1, status: 'active', tags: ['new'], priority: 'high' };
const result = diff(original, updated);
// { status: 'active', priority: 'high' }Nested Object Diff
ts
import { diff } from '@vielzeug/toolkit';
const v1 = {
user: {
name: 'Alice',
settings: { theme: 'dark' },
},
};
const v2 = {
user: {
name: 'Alice',
settings: { theme: 'light' },
},
};
diff(v1, v2);
// { user: { settings: { theme: 'light' } } }Implementation Notes
- Returns properties from
bthat are different froma. - Uses deep equality (
isEqual) for comparisons. - If a property exists in
abut is missing inb, it is currently not included in the diff (use for "update/patch" logic). - Throws
TypeErrorif either argument is not an object.