diffjsons
v0.1.0
Published
A structural JSON diff for humans and CI.
Downloads
32
Maintainers
Readme
jsondiff
A structural JSON diff for humans and CI.
jsondiff compares two JSON documents by structure and values, not by text. Key order, whitespace, and formatting do not matter. The result is a readable terminal diff, compact one-liners, a summary, or deterministic JSON — plus reliable exit codes for scripts.
Why structural diffing matters
A line-by-line text diff treats these as completely different files:
{"name":"John","age":31}{
"age": 31,
"name": "John"
}They are the same JSON value. jsondiff reports them as equal.
When something actually changes, the tool points at the path — user.profile.name, tags[2], ["user.name"] — instead of dumping two whole objects.
Installation
npm i -g diffjsonsThat installs the diffjsons command.
Or run it without installing:
npx diffjsons old.json new.jsonRequires Node.js 20 or later.
Quick start
diffjsons old.json new.jsondiffjsons
old.json → new.json
────────────────────────────────────────────
tags[2]
+ "opensource"
user.address.city
+ "Toronto"
user.age
- 31
+ 32
user.name
- "John"
+ "John Smith"
users[0].email
- "[email protected]"
+ "[email protected]"
Summary
────────────────────────────────────────────
+ 2 additions
- 0 removals
~ 3 modifications
Status: CHANGEDSupported JSON types
Objects, arrays, strings, numbers, booleans, null, and any nesting of those.
Comparison uses JSON.parse values. 1 and 1.0 are equal because they parse to the same JavaScript number. There is no floating-point tolerance.
Object comparison
Objects are compared by key. Key order in the file does not affect the result.
diffjsons <(echo '{"b":2,"a":1}') <(echo '{"a":1,"b":2}')Exit code 0.
Array comparison
Arrays are not stringified and marked changed as a whole.
Default strategy: LCS-anchored leftover pairing.
- Find a longest common subsequence of deep-equal elements (anchors).
- Between anchors, pair leftover elements in order and recurse.
- Extra leftovers are additions or removals.
This is why an insert is an insert:
["a", "b", "c"] vs ["a", "x", "b", "c"] → [1] + "x"And why an object field change stays a field change:
users[0].name
- "John"
+ "John Smith"Reordered arrays ([1,2,3] vs [3,2,1]) are differences. Use --sort-arrays if order should not matter.
If both array lengths multiply to more than 4,000,000, jsondiff falls back to index alignment.
Match object arrays by key
id is not special. Identity matching is opt-in:
diffjsons old.json new.json --array-key id- Elements are matched by
JSON.stringifyof that property. - Duplicate keys on the same side are an input error (exit
3). - A missing key makes that element unmatched (added or removed).
- If the array contains non-objects, the default LCS algorithm is used instead.
Ignore paths
diffjsons old.json new.json --ignore metadata.timestamp
diffjsons old.json new.json --ignore "metadata.*" --ignore "users.*.password"Syntax:
| Pattern | Meaning |
|---|---|
| metadata.timestamp | That path and nothing else |
| metadata.* | Every direct child of metadata (and their subtrees) |
| metadata | The whole metadata subtree, including add/remove |
| users[0].name | Index sugar |
| users.*.password | Password on every user |
| ["user.name"] | A literal key that contains a dot |
* matches exactly one path segment. There is no ** and no jq/JSONPath.
Sort arrays
diffjsons old.json new.json --sort-arraysEach array is copied and sorted for comparison only. Input files are never rewritten.
- Duplicates are kept (multiset).
- Sort order: null, boolean, number, string, array, object; then a deterministic value order.
- Paths use post-sort indices.
Output modes
diffjsons old.json new.json --compact
diffjsons old.json new.json --summary
diffjsons old.json new.json --json
diffjsons old.json new.json --quiet--compact:
+ user.address.city = "Toronto"
~ user.name: "John" → "John Smith"
- user.age = 31--json is deterministic: no timestamps, no absolute paths, no ANSI. The path field uses the same formatted paths as the terminal ("" for the document root).
--quiet prints nothing. Use the exit code.
Only one of --json, --summary, --compact, --quiet may be set.
Color
Default: color when stdout is a TTY.
--colorforces ANSI--no-colordisables ANSI (wins if both are passed)NO_COLOR(any non-empty value) disables color unless--coloris set
JSON and quiet modes never emit ANSI.
Stdin
stdin is the old document. Exactly one file is required: the new document.
diffjsons --stdin new.json < old.json
cat old.json | diffjsons --stdin new.jsonTwo files plus --stdin, or --stdin with no file, is a usage error.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Documents are identical |
| 1 | Documents differ |
| 2 | Invalid usage |
| 3 | Missing file, unreadable file, invalid JSON, or duplicate --array-key |
CI
if diffjsons expected.json actual.json --quiet; then
echo "JSON matches"
else
echo "JSON differs"
exit 1
fiLibrary API
import { diffJson, formatPath } from "diffjsons";
const result = diffJson(
{ name: "John", tags: ["dev"] },
{ name: "John Smith", tags: ["dev", "oss"] },
);
console.log(result.changed);
console.log(result.summary);
console.log(formatPath(result.changes[0].path));diffJson(left, right, {
sortArrays?: boolean;
arrayKey?: string;
ignore?: string[];
})The CLI uses this same function. There is no second comparison implementation.
Numbers
Equality follows JavaScript JSON.parse:
1and1.0are equal0and-0are equal- Integers beyond
Number.MAX_SAFE_INTEGERmay lose precision - There is no epsilon / tolerance
Performance
Designed for developer-sized JSON (configs, API fixtures, snapshots).
- 32 MiB maximum per input
- LCS for arrays until
len(left) * len(right) > 4_000_000, then index alignment - No streaming parser
Limitations
- Not a text diff; whitespace-only changes are ignored
- Reordered arrays are differences unless
--sort-arrays --sort-arrayspaths use post-sort indices--array-keyis opt-in;idis not inferred- Very large arrays use index alignment
- JavaScript IEEE-754 numbers only
- Max 32 MiB per file
- JSON only — no YAML, TOML, or XML
- No RFC 6902 patch output
No telemetry
jsondiff makes no network requests. Comparison happens entirely on your machine. There is no analytics, tracking, or phone-home.
Development
npm install
npm test
npm run lint
npm run typecheck
npm run buildSee CONTRIBUTING.md.
