Skip to content

Developer Experience

Fluent DiffView toolbox — explain, filter, serialize, patch, and stats over a DiffResult without mutating diff() output.

Previous: Serialization · Next: Engines · Back to: Overview

Import path

ts
import { diff } from "@jayoncode/object-diff";
import { createDiffView } from "@jayoncode/object-diff/view";

createDiffView is /view only (not on root). Canonical map: Engines.

Why DiffView?

NeedUse
Plain data for storage / wirediff()DiffResult
Review, filter, explain, patch from that resultcreateDiffView(result)

DiffResult stays serializable data. DiffView is the developer toolbox on top — same architecture lock as the package ADR (no methods attached to diff() return values).

Fluent API

ts
import { diff } from "@jayoncode/object-diff";
import { createDiffView } from "@jayoncode/object-diff/view";

const view = createDiffView(diff(before, after, { detectMoves: true }))
  .exclude(["password"])
  .updated();

view.serialize("markdown");
view.patch();
view.statistics();
view.explain(); // structured DiffExplanation[]
view.explain({ format: "human" }); // review text
  • Free functions remain canonical (diff, serialize, patch, …)
  • createDiffView is opt-in on /view (tree-shake friendly)
  • Chaining returns new views — the source DiffResult is never mutated

explain()

Turn change records into review-friendly explanations (especially moves / identity):

ts
const result = diff(before, after, { identityKey: "id", detectMoves: true });
const view = createDiffView(result);

view.explain({ identityKey: "id" });
// [
//   {
//     path: "[2]",
//     type: "moved",
//     reason: "Matched using identityKey 'id'",
//     confidence: "high",
//     summary: "item moved",
//     from: "[0]",
//   },
//   {
//     path: "name",
//     type: "changed",
//     reason: "Primitive value changed",
//     confidence: "high",
//     summary: "`name` updated",
//     previous: "John",
//     current: "Johnny",
//   },
// ]

view.explain({ format: "human", identityKey: "id" });
// ✓ item moved
//   index 0 → 2
//   matched using id
//
// ✓ name updated
//   John → Johnny
OptionDefaultMeaning
format"structured""structured"DiffExplanation[]; "human" → string
identityKeyProperty name hint when the diff used identity matching (improves move copy)

DiffResult does not store the options used to produce it — pass identityKey again when you want identity-aware wording.

Prefer explain({ format: "human" }) for reviews; keep serialize("human") for compact changelog-style bullets.

Subpath map

Canonical table: Engines.

ImportUse
@jayoncode/object-diff/viewcreateDiffView + explain
@jayoncode/object-diff/statsstatistics
@jayoncode/object-diff/queryfind / filter / query()

Pitfalls

  • Do not expect createDiffView on the root entry.
  • Free functions remain canonical; the view does not mutate DiffResults.
  • explain({ identityKey }) is a hint for wording, not a re-diff — run diff(..., { identityKey }) first.

Errors

All thrown errors extend ObjectDiffError (itself an Error), carrying a machine-readable code and optional details:

ts
import { ObjectDiffError } from "@jayoncode/object-diff";

try {
  diff(circularA, circularB); // circular: "error" (default)
} catch (error) {
  if (error instanceof ObjectDiffError) {
    error.code; // e.g. "circular_reference"
    error.details; // e.g. { path: "user.self" }
    error.cause; // original cause, when the error wraps another (e.g. PluginError)
  }
}
ClasscodeThrown by
CircularReferenceErrorcircular_referencediff/compare when a repeated reference is found and circular: "error" (default)
MaxDepthExceededErrormax_depth_exceededdiff/compare when traversal depth exceeds maxDepth
InvalidPatchErrorinvalid_patchpatch, applyPatch, validatePatch on malformed ops or unsafe path segments (__proto__, constructor, prototype)
PatchApplyErrorpatch_apply_errorapplyPatch when a test op fails, or a path cannot be resolved on the target
InvalidOptionsErrorinvalid_optionsDuplicate identityKey ids, unknown serialize format or merge strategy names, duplicate/colliding formatter plugin names
UnsupportedTypeErrorunsupported_typeReserved for value kinds the core cannot classify (currently unused by shipped code paths)
NotImplementedErrornot_implementedReserved for stubbed/future functionality
PluginErrorplugin_error/plugins createEngine — duplicate/invalid plugin shape, or a hook callback throwing (original error is cause)

All error classes are exported from the root entry, /core, and their owning subpath (e.g. PluginError from /plugins).

Playground

Interactive Lab tabs cover Moves, Patch (apply/revert), Merge conflicts, Explain (view.explain()), and Perf (hasChanges vs full diff). Open the Array Reorder experiment to jump straight into moves + explain.

Open Object Diff Lab →

An ecosystem of independent, headless TypeScript libraries engineered for modern web development. Every package includes interactive playgrounds and documentation that evolves alongside the code.