Guide
This guide walks through the core workflow of @opentf/obj-diff: compute a diff, read it, apply it, and customize it. Every snippet assumes:
import { diff, patch, diffWith } from "@opentf/obj-diff";
1. Compute a diff
diff(a, b) returns an array of operations that turn a into b. Each operation has a type, a path, and (for additions and changes) a value.
const a = { a: 1, b: 2 }; const b = { a: 2, c: 3 }; diff(a, b); /* [ { type: 2, path: ["a"], value: 2 }, // CHANGED { type: 0, path: ["b"] }, // DELETED { type: 1, path: ["c"], value: 3 }, // ADDED ] */
The path is an array you can walk or join. It points at exactly what changed — see the API Reference for the full op-type table.
2. Read the result
Because the result is plain data, you can filter and inspect it however you like:
const changes = diff(a, b); changes.length; // how many operations changes.filter((c) => c.type === 0); // just the deletions changes.map((c) => c.path.join(".")); // ["a", "b", "c"]
This is what makes the diff useful as an audit log, a sync payload, or a dirty-field marker — see Use Cases.
3. Apply a diff (patch)
patch(obj, changes) replays a diff onto an object and returns the result. The round-trip always holds — patching a with diff(a, b) reproduces b:
const a = { foo: { bar: [1, 2] } }; const b = { foo: { bar: [1] } }; const changes = diff(a, b); const result = patch(a, changes); // deep-equal to b
4. Work with native collections
Unlike JSON-only differs, obj-diff compares Map, Set, Date, TypedArray, and more directly — no conversion step.
// Set — compared positionally, by insertion order diff(new Set([1, 2]), new Set([2, 3])); //=> [ { type: 2, path: [0], value: 2 }, { type: 2, path: [1], value: 3 } ] // TypedArray — compared element-wise, and fast diff(new Uint8Array([1, 2, 3]), new Uint8Array([1, 4, 3])); //=> [ { type: 2, path: [1], value: 4 } ]
5. Compact array diffs
Array edits produce the shortest edit script — one operation per real insert or remove, not one per shifted element:
diff([1, 2, 3], [0, 1, 2, 3]); //=> [{ type: 3, path: [0], value: 0 }] // INSERTED at index 0 diff([1, 2, 3, 4], [1, 4]); //=> [{ type: 4, path: [1] }, { type: 4, path: [1] }] // REMOVED (application-time indexes)
See the Benchmarks for how this compares to positional differs.
6. Circular references are safe
Recursive structures are compared without infinite loops:
const a = { id: 1 }; a.self = a; const b = { id: 2 }; b.self = b; diff(a, b); //=> [{ type: 2, path: ["id"], value: 2 }]
7. Custom equality
When two values should be treated as equal by your own rule (for example MongoDB ObjectIds), pass a comparator to diffWith. It runs only when both values are objects:
import { ObjectId } from "bson"; diffWith(recordA, recordB, (x, y) => { if (x instanceof ObjectId && y instanceof ObjectId) { return x.toString() !== y.toString(); } });
Next steps
API Reference — every function, type, and constant.
Caveats & FAQs — aliasing, symbol keys, move detection, and more.
Use Cases — where a structural diff pays off.