Skip to main content

jsondiffview

jdv produces deterministic, JSON-shaped review text for humans. It compares strict JSON only: the first argument is the old (before) document and the second is the new (after) document.

The output is annotated review text, not valid JSON, JSON Patch, or an applyable patch. Additions (+), removals (-), modifications (~), moves (>), omissions (…), and intraline spans ([-old-], [+new+]) remain readable without color.

Terminal screenshot of jdv reviewing nested JSON changes, moves, additions, and removals

The package published on PyPI is named jsondiffview; the import package is also jsondiffview, while the installed CLI command is jdv.

Upgrading from 2.1.2? Read Migrating to 3.0.0. Version 3 publication uses only validated GitHub Release archives and the protected PyPI Trusted Publisher workflow.

Install and run

For development:

uv sync
uv run jdv --help

Or build and install the local wheel:

uv build --no-sources
uv tool install ./dist/jsondiffview-3.1.0-py3-none-any.whl

Both entry points use the same implementation:

jdv before.json after.json
python -m jsondiffview before.json after.json

One input may be read from stdin:

cat before.json | jdv - after.json

Copy-ready PowerShell commands and editable sample documents are available in the playground.

Command

Usage: jdv [OPTIONS] OLD_JSON NEW_JSON

  Compare strict OLD_JSON (before) with NEW_JSON (after).

Options:
  -v, --view [summary|review|full]
                                  Detail level.  [default: review]
  -k, --match-key FIELD           Array-object identity key; repeat in
                                  priority order. Any occurrence replaces
                                  defaults: id, key, name, title.
  -c, --color [auto|always|never]
                                  Review color policy.  [default: auto]
  -q, --quiet                     Suppress the equality notice only.
  --version                       Show the version and exit.
  -h, --help                      Show this message and exit.

Option choices and match-key names are case-sensitive. Repeating --match-key preserves command-line priority and replaces all default keys; supplying the same key twice is an error. -- ends option parsing for paths that begin with -. A file literally named - is not addressable, and both arguments cannot use stdin.

Views

  • review (default) expands every changed, added, and removed value and keeps one adjacent unchanged sibling around each changed run.
  • summary collapses every unchanged sibling run and may summarize large wholly added or removed containers with a bounded first-two-child preview and exact omitted-descendant count.
  • full expands all structural context and added/removed subtrees.

All views use the same semantic comparison. Deterministic long-string safety limits remain active even in full.

Status and stream contract

Status Meaning stdout stderr
0 Documents are equal empty No semantic differences.
1 Differences were reviewed successfully one review empty
2 Usage, input, parse, or output error empty unless output failed one diagnostic

Status 1 is an expected diff-style result, not an operational failure. --quiet suppresses only the equality notice. A closed stdout pipe exits 2 without a diagnostic or traceback.

A CI-safe shell pattern is:

set +e
jdv --color never before.json after.json
status=$?
set -e
if [ "$status" -gt 1 ]; then
  exit "$status"
fi

Review format

Every physical line starts with a two-column semantic prefix. Object keys are JSON strings; array entries use new indexes such as [2]:, while unmatched removals use old indexes such as [old 4]:.

~ {
~   "services": [
      [0]: {
        "id": "api",
…       1 unchanged field omitted
      },
>     moved from $.services[2] to $.services[1] (matched by "id": "db")
      [1]: {
        "id": "db",
…       1 unchanged field omitted
      },
~     [2]: {
        "id": "worker",
~       "port": 9000 -> 9001
      }
    ],
~   "title": "Hello [-world-][+team+]",
-   "obsolete": true
  }

Modified short strings use Unicode grapheme-safe word-, whitespace-, and punctuation-segment spans. When one identifier or number segment has a useful shared prefix or suffix, finer grapheme detail is used without splitting a user-perceived character. Multiline strings are compared as exact logical lines, with line terminators visible as JSON escapes; sufficiently similar replacement lines can show side-specific intraline spans. Over-budget multiline changes retain bounded first/last previews with exact omitted line and code-point counts. Adaptive classification keeps prose inline while routing dense separators and opaque runs to bounded hunk output. Long single-line strings report Unicode code-point lengths, half-open offsets, display-cell-bounded excerpts, and exact omission counts. All matching and output limits are deterministic work counters, including in full. Strings shown only on one side or as unchanged context are also bounded at 512 code points, including values containing line breaks.

Every string excerpt is limited to 512 code points, including its omission marker, as well as the view's display-cell allowance. Zero-width characters and combining sequences cannot bypass that limit. An indivisible grapheme that does not fit is omitted whole, with an exact omitted-code-point count; it is never split. These limits also apply to long-string hunk context and identity values in move explanations.

Terminal controls and Unicode bidirectional controls are displayed as visible JSON escapes in keys, values, move explanations, and diagnostics. Ordinary Arabic, Hebrew, and other scripts remain readable; emoji joiners and text joiners are preserved rather than escaping every Unicode format character.

The main review above includes a pure move (db). Array removals retain their old index:

~ [
    [0]: "keep",
-   [old 1]: "gone"
  ]

A moved entry can also contain a nested modification:

~ [
>   moved from $[1] to $[0] (matched by "id": "b")
~   [0]: {
      "id": "b",
~     "v": 1 -> 2
    },
    [1]: {
      "id": "a",
…     1 unchanged field omitted
    }
  ]

Ambiguous identity values remain explicit additions and removals:

~ [
+   [0]: {
+     "id": "x",
+     "v": 3
+   },
+   [1]: {
+     "id": "x",
+     "v": 4
+   },
-   [old 0]: {
-     "id": "x",
-     "v": 1
-   },
-   [old 1]: {
-     "id": "x",
-     "v": 2
-   }
  ]

Color is presentation only: yellow marks modifications, red removals, green additions, and cyan move provenance. --color auto requires a terminal and an absent or empty NO_COLOR; always overrides both redirection and NO_COLOR; never disables ANSI. FORCE_COLOR is intentionally ignored.

Strict JSON and matching

Inputs use RFC JSON syntax with unique object names and project-owned numeric limits:

  • UTF-8 and an optional leading UTF-8 BOM are accepted.
  • Duplicate object keys, NaN, Infinity, and -Infinity are rejected.
  • Integer literals are limited to 4,300 digits.
  • Decimal/exponent lexemes are limited to 10,000 code points.
  • Container nesting is limited to 256 levels; there is no global file-size limit.
  • A decimal/exponent value whose binary64 conversion is non-finite, such as 1e999, is rejected.
  • Integer 1, decimal/exponent 1.0, boolean true, and string "1" are distinct. Decimal spellings 1.0 and 1.00 compare equal.
  • Object member order is ignored; array order matters.

Array entries are never paired by position or fuzzy similarity. Object entries pair only when a configured identity value is unique on both sides. The defaults are id, key, name, and title; use -k FIELD to replace them. Remaining globally unique entries pair by exact strict value. Repeated exact values may align monotonically to stabilize the review, but that alignment is not identity evidence and never fabricates a move. Other ambiguous values remain a removal plus an addition. Null, object, and array identity-key values are unavailable, and a lower-priority key cannot override conflicting higher-priority scalar identities.

Moves are the minimal deterministic relative-order changes among trusted matches, subject to keeping duplicate exact-value alignments stationary. Those evidence-less alignments form mandatory monotone anchors: a trusted match crossing an anchor must be shown as moved, not silently called unchanged. Insertions and removals that merely shift absolute indexes do not mark every survivor as moved. A moved entry keeps its old/new paths and can also show nested modifications.

Development

uv sync --locked --group dev
uv run --locked pytest -q
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked mypy src tests scripts
uv run --locked python -m jsondiffview --help
uv run --locked jdv --version
uv build --no-sources

The package supports CPython 3.11 and newer.

The review-core audit and design decisions record reproduced defects, the constrained move algorithm, resource-bound tests, benchmark methodology, and remaining redesign opportunities. Focused, non-gating measurements can be reproduced with:

uv run --locked python benchmarks/benchmark_review_invariants.py --case full-context
uv run --locked python benchmarks/benchmark_review_invariants.py --case mixed-duplicates
uv run --locked python benchmarks/benchmark_review_invariants.py --case zero-width

Release publication

.github/workflows/publish.yml is the only publication surface. A final GitHub Release must point to a commit with a successful Quality run and contain exactly the expected wheel and sdist. The workflow downloads those archives without rebuilding them, validates their Metadata 2.5 contents, and compares exact filenames and SHA-256 digests with PyPI. The SHA-pinned PyPA action v1.14.0 bundles Twine 6.1.0 and Packaging 25.0. Before OIDC starts, the workflow stages Packaging 26.2 from uv.lock, verifies both its pinned wheel and source-tree SHA-256, and installs a fail-closed parser overlay ahead of the action's bundled dependency. The overlay does not change either archive.

An exact existing release is a successful no-op. Conflicting or unexpected files stop publication; a partial exact release stages only its missing files. Any required upload uses the configured PyPI Trusted Publisher and the pypi GitHub environment, then the workflow polls for the exact files and verifies a clean index install through both jdv and python -m jsondiffview.

Maintainers should require approval by a trusted reviewer and configure selected deployment refs separately for branch main and tags v*. Manual dispatch is accepted only from main; a release: published run uses its matching tag ref. The same workflow therefore handles future published releases and deliberate repeat dispatches for existing final release tags.

Metadata

Release files for jsondiffview 3.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jsondiffview 3.1.0
File Size Uploaded
jsondiffview-3.1.0.tar.gz 193.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jsondiffview 3.1.0
File Interpreter ABI Platform
jsondiffview-3.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 237.1 kB

Release files / jsondiffview-3.1.0.tar.gz

Download URL jsondiffview-3.1.0.tar.gz
Size 193.2 kB
Tags Source
SHA-256 checksum
How to use checksums
86a626a0c68c99a4d44017bd5a80ebe42020d54308d42e8e8e3bfc72122b5fee
BLAKE2b-256 checksum
How to use checksums
8901d2f5409d9bd1195c4f2b9591c8385d67ede6633243755552772bfe21ca10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / jsondiffview-3.1.0-py3-none-any.whl

Download URL jsondiffview-3.1.0-py3-none-any.whl
Size 43.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
870f4b9e7127566130f3b4bcc8925c7455c85439c88b9773a222c64ece149ce2
BLAKE2b-256 checksum
How to use checksums
afe3651417ec29169c5995150bd7f2e54e08788e28093323e91c998c911570a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.1.0 This release

2 release files

3.0.0

2 release files

2.1.2

2 release files

2.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page