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:

JavaScript
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.

JavaScript
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:

JavaScript
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:

JavaScript
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, Temporal, and more directly — no conversion step.

JavaScript
// 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 } ]

// Temporal — compared as immutable values
diff({ d: Temporal.PlainDate.from("2020-01-01") },
     { d: Temporal.PlainDate.from("2021-06-15") });
//=> [ { type: 2, path: ["d"], value: <Temporal.PlainDate 2021-06-15> } ]
Temporal values are atomic

A change replaces the whole value rather than diffing its fields. Duration is compared structurally (by its canonical string), because two equal-length durations expressed differently (PT2H vs PT120M) are genuinely different values.

5. Compact array diffs

Array edits produce the shortest edit script — one operation per real insert or remove, not one per shifted element:

JavaScript
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:

JavaScript
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:

JavaScript
import { ObjectId } from "bson";

diffWith(recordA, recordB, (x, y) => {
  if (x instanceof ObjectId && y instanceof ObjectId) {
    return x.toString() !== y.toString();
  }
});
The comparator only sees object pairs

diffWith calls your comparator only when both values are objects. For primitive pairs it falls back to built-in equality, so returning undefined (as above, for non-ObjectIds) lets the default behavior take over.

8. Send a diff over the wire

To move a diff between processes — client → server, a Web Worker, storage — JSON.stringify isn't enough (it turns Dates into strings and Map/Sets into {}). Use serialize / deserialize, which preserve every type:

JavaScript
import { serialize, deserialize } from "@opentf/obj-diff";

const wire = serialize(diff(a, b));     // a type-safe JSON string
patch(a, deserialize(wire));            // Date / Map / Set / … restored exactly

See Serialization for the format and when to use it.

Next steps

Last updated on
Edit this page