Comparison

There are many JavaScript diffing libraries. Most were designed for JSON — plain objects, arrays, and primitives. @opentf/obj-diff is designed for real JavaScript values: Map, Set, Date, RegExp, TypedArray, ArrayBuffer, class instances, and circular references, with a patch() that replays a diff back onto the original.

Correctness across native types

The most important difference is not speed — it's whether a library notices a change at all. The table below is generated by the repository's accuracy benchmark (benchmarks/collect.mjs). ❌ means the library silently reported no difference for two values that were genuinely different — the most dangerous kind of wrong answer.

LibraryMapsSetsDatesRegExpsTypedArrayCircular
obj-diff
microdiff
deep-diff
deep-diff-ts
deep-object-diff
just-diff
@adobe/optimized-diff
recursive-diff
jsondiffpatch

Map and Set are the common blind spot: change a value in a Map and most libraries report [].

Capabilities

Capability@opentf/obj-diffNotes
Deep diffing at any depth
patch() to reconstruct the targetRound-trips: patch(a, diff(a, b)) deep-equals b.
Compact array diffs (Myers LCS)One op per real insert/remove — see Benchmarks.
Native Map / Set / Date / TypedArrayNo "convert to JSON first" step.
Circular-reference safe
Custom equality via diffWith()e.g. compare MongoDB ObjectIds by string.
TypeScript-nativeFull types, no @types package needed.
Move detectionReorders are expressed as remove + insert — see Caveats.
RFC 6902 (JSON Patch) wire formatIntentional — see below.

Type-safe serialization vs superjson

Diffing is only half the job — a diff usually has to travel. The same codec that powers serialize/deserialize (for diffs) is exposed as stringify/parse for any value — the same job as superjson: keep real JavaScript types alive across a JSON boundary. The tables below run stringify/parse against superjson v2.2.6 on identical values, generated by benchmarks/serialization.mjs.

Type fidelity

Round-trip a value, then check it comes back as the same type with the same data. ❌ is a silent loss (the value survives but wrong); 💥 throws.

Valueobj-diffsuperjson
Plain nested object
Date
Invalid Date
RegExp
Map
Set
BigInt
Int8Array
Float64Array [NaN,-0,∞]
BigInt64Array💥
ArrayBuffer
DataView
Error + custom prop
Boxed Number
Boxed String
undefined
NaN
-0
Infinity
URL
Circular reference
Circular array
Round-trip score22/2214/22
These failures are silent

obj-diff restores every native type. superjson degrades Float64Array specials (NaN/-0/0), turns ArrayBuffer/DataView into plain objects, unwraps boxed primitives, and drops an Error's own props — without throwing. Only BigInt64Array errors outright; the rest return a wrong value that flows straight into your app.

Wire size

Valueobj-diffsuperjson
Flat object41 B44 B
Mixed native types171 B153 B
Typed array (8)71 B96 B
Nested record192 B192 B
Tricky numbers132 B115 B
Circular graph67 B83 B

Comparable overall: obj-diff is smaller for leaf and native-heavy values (no {json,meta} envelope), while superjson's single shared meta table wins when many special values cluster in one object.

Speed

Librarystringifyparseround trip
obj-diff8.17 µs4.08 µs13.3 µs
superjson24.9 µs5.75 µs27.7 µs

obj-diff is consistently faster — superjson walks each value twice (once for json, once to record meta paths), where obj-diff encodes in a single pass. Absolute times are machine-dependent; the gap is the signal.

When to choose something else

obj-diff is not trying to be every diffing tool. Reach for an alternative when:

  • You need the interoperable RFC 6902 JSON Patch format (to send patches to a non-JS service, or apply them with a standard library) → use fast-json-patch or rfc6902. obj-diff uses its own compact, JS-native op format instead; see the FAQ.

  • Your arrays are large and frequently reordered and you need true move operations → a move-detecting differ will produce smaller diffs for shuffles. obj-diff treats a move as a remove plus an insert.

  • You only ever touch flat JSON and want the absolute lowest nanosecond count on tiny objects → a minimal positional differ like microdiff shaves a few hundred nanoseconds. For anything with native types, collections, or where diff size matters, obj-diff wins on the axes that count.

Last updated on
Edit this page