API Reference

Everything is a named export from the package root:

TypeScript
import {
  diff, patch, diffWith,           // functions
  serialize, deserialize,          // diff wire format
  stringify, parse,                // general-purpose value codec
  DELETED, ADDED, CHANGED, INSERTED, REMOVED, // op-type constants
} from "@opentf/obj-diff";
ExportKindSummary
difffunctionDeep-compare two values into a list of operations.
patchfunctionApply a diff to reconstruct the target.
diffWithfunctiondiff with a custom equality function.
serializefunctionEncode a diff to a self-describing JSON string.
deserializefunctionDecode a serialized diff back into live typed values.
stringifyfunctionEncode any value to a type-safe JSON string.
parsefunctionDecode a stringify string back into live typed values.
DiffResulttypeThe shape of a single operation.
Op constantsconstDELETED, ADDED, CHANGED, INSERTED, REMOVED.

diff

TypeScript
function diff(a: unknown, b: unknown): DiffResult[]

Performs a deep comparison and returns the list of operations that transform a into b. An empty array means the two values are deeply equal.

Parameters

NameTypeDescription
aunknownThe source (original) value.
bunknownThe target value to compare against.

ReturnsDiffResult[], in application order.

TypeScript
diff({ a: 1 }, { a: 2 });
//=> [{ type: 2, path: ["a"], value: 2 }]

Compact array diffs

diff trims the common prefix and suffix of arrays and resolves the remaining middle with a bounded shortest-edit-script (Myers LCS) pass. Inserting one element at the front of a 10,000-element array produces 1 op, not 10,001:

TypeScript
diff([1, 2, 3], [0, 1, 2, 3]);
//=> [{ type: 3, path: [0], value: 0 }]

diff([1, 2, 3, 4], [1, 4]);
//=> [{ type: 4, path: [1] }, { type: 4, path: [1] }]

INSERTED / REMOVED indexes are application-time (RFC 6902 style): apply the ops in order and each index is valid at the moment it is applied. When two arrays are too different (an internal edit-distance cap is exceeded), diff falls back to index-by-index comparison to bound worst-case time and memory.


patch

TypeScript
function patch(obj: unknown, patches: DiffResult[]): unknown

Applies a diff to obj and returns the reconstructed value. The round-trip always holds: patch(a, diff(a, b)) is deep-equal to b.

Parameters

NameTypeDescription
objunknownThe source value to apply the diff to.
patchesDiffResult[]A diff produced by diff or diffWith.

Returns — the patched value.

TypeScript
const a = { a: 1 };
const b = { a: 2 };

patch(a, diff(a, b)); //=> { a: 2 }
Note

Added and changed values are placed into the result by reference, not cloned. See Caveats for aliasing details.


diffWith

TypeScript
function diffWith(
  a: unknown,
  b: unknown,
  isChanged: (a: object, b: object) => boolean | undefined,
): DiffResult[]

Like diff, but you supply a comparator to override equality for specific object types (e.g. MongoDB ObjectId, custom classes).

Parameters

NameTypeDescription
aunknownThe source value.
bunknownThe target value.
isChanged(a, b) => boolean | undefinedReturn true/false to force the result, or undefined to fall back to the built-in comparison. Invoked only when both values are objects.

ReturnsDiffResult[].

TypeScript
import { diffWith } from "@opentf/obj-diff";
import { ObjectId } from "bson";

diffWith(recordA, recordB, (a, b) => {
  if (a instanceof ObjectId && b instanceof ObjectId) {
    return a.toString() !== b.toString();
  }
});

serialize

TypeScript
function serialize(diff: DiffResult[]): string

Encodes a diff into a self-describing JSON string that survives transport. A diff holds live values by reference, so JSON.stringify loses their types; serialize preserves them. See Serialization for the format and rationale.

Parameters

NameTypeDescription
diffDiffResult[]A diff produced by diff or diffWith.

Returns — a JSON string. Preserves native types (Date, Map, Set, TypedArray, URL, …) plus circular references and shared identity among plain objects/arrays. Throws on symbols, functions, class-instance values, and cycles that run through a Map/Set/Error.

TypeScript
const wire = serialize(diff(a, b));

deserialize

TypeScript
function deserialize(wire: string): DiffResult[]

Decodes a string from serialize back into a diff whose values are live, correctly-typed objects — ready for patch.

Parameters

NameTypeDescription
wirestringThe output of serialize.

ReturnsDiffResult[] with reconstructed values.

TypeScript
patch(a, deserialize(wire)); // Date / Map / Set / … restored exactly
Temporal at decode time

Deserializing a Temporal value requires a Temporal implementation on globalThis (native or a globally-installed polyfill). This applies to parse too. Encoding never needs one.


stringify

TypeScript
function stringify(value: unknown): string

The general-purpose counterpart to serialize: encodes any JavaScript value — not just a diff — into a self-describing, type-safe JSON string. Uses the same codec, so it preserves every supported native type (Date, Map, Set, TypedArray, ArrayBuffer, DataView, Error, URL, BigInt, Temporal, NaN/±Infinity/-0, undefined, …) plus circular references and shared identity among plain objects/arrays. A type-preserving drop-in for JSON.stringify. See the superjson comparison.

Parameters

NameTypeDescription
valueunknownAny serializable JavaScript value.

Returns — a JSON string. Throws on symbols, functions, and class-instance values.

TypeScript
const wire = stringify({ when: new Date(), tags: new Set([1, 2]), big: 42n });

parse

TypeScript
function parse<T = unknown>(wire: string): T

Decodes a string from stringify back into the original value with its types restored — the type-preserving counterpart to JSON.parse.

Parameters

NameTypeDescription
wirestringThe output of stringify.

Returns — the reconstructed value (typed as T).

TypeScript
const value = parse(wire); // Date / Set / BigInt restored

DiffResult type

TypeScript
type DiffResult = {
  type: 0 | 1 | 2 | 3 | 4; // see the op-type table below
  path: Array<unknown>;    // path to the property; Map entries use the Map key itself
  value?: unknown;         // the new value (present for ADDED, CHANGED, INSERTED)
};

Op-type constants

ValueConstantMeaningApplies to
0DELETEDKey no longer exists (delete obj.k / map.delete(k); on an array index it leaves a hole).objects, Maps, arrays (sparse)
1ADDEDNew key/value (on arrays: assign at index).objects, Maps, Sets, arrays
2CHANGEDValue replaced at the path.all
3INSERTEDInsert value at the index, shifting later elements right (splice(i, 0, v)).arrays only
4REMOVEDRemove the element at the index, shifting later elements left (splice(i, 1)).arrays only
TypeScript
import { DELETED, ADDED, CHANGED, INSERTED, REMOVED } from "@opentf/obj-diff";

diff(a, b).filter((op) => op.type === REMOVED);

An empty path ([]) denotes the root — the entire source was replaced by the target (e.g. comparing an object to null).

Last updated on
Edit this page