webdevpuneet.com

JSON Diff Viewer — Free Recursive Structural JSON Comparator

JSON Diff Viewer · Tools · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="jdv-wrap">
  <span class="jdv-tag">recursive structural diff</span>
  <h1>JSON diff viewer</h1>

  <div class="jdv-inputs">
    <div class="jdv-input-col">
      <label for="jdvBefore">Before</label>
      <textarea id="jdvBefore" spellcheck="false"></textarea>
    </div>
    <div class="jdv-input-col">
      <label for="jdvAfter">After</label>
      <textarea id="jdvAfter" spellcheck="false"></textarea>
    </div>
  </div>

  <div class="jdv-legend">
    <span class="jdv-legend-item"><i class="jdv-swatch added"></i>Added</span>
    <span class="jdv-legend-item"><i class="jdv-swatch removed"></i>Removed</span>
    <span class="jdv-legend-item"><i class="jdv-swatch changed"></i>Changed</span>
  </div>

  <div class="jdv-output" id="jdvOutput"></div>
  <p class="jdv-error" id="jdvError" hidden></p>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

True recursive diff
Walks real JS object/array structure, not text lines.
Correct nested handling
Changes at any depth report at their exact location.
Added/removed/changed tags
Every value gets one precise status, not a guess.
Old → new value display
Changed primitives show both sides explicitly.
Formatted, indented output
Bracket-correct pretty-printed JSON with inline markers.
Live debounced diffing
Recomputes 200ms after the last keystroke.
Friendly parse errors
Names which input is invalid JSON and why.
Zero dependencies
No diff library — genuine recursive comparison logic.

About this UI Snippet

JSON Diff Viewer — A Real Structural Diff, Not a Text Diff

Screenshot of the JSON Diff Viewer snippet rendered live

A text/line diff on two pretty-printed JSON blobs is famously misleading — reordering keys or reformatting whitespace produces a wall of red and green even when the data is identical. This tool instead parses both inputs and walks them recursively as JavaScript values, so the diff reflects actual structural differences.

The recursive comparison: diffValue()

diffValue(a, b) branches on the actual types of both values. If both are plain objects, it unions their key sets, sorts them for stable output, and recurses into diffValue for every key present in both — keys only in a are tagged removed, keys only in b are tagged added. If both are arrays, it walks index by index the same way, tagging extra trailing indices as added or removed. For anything else (primitives, or mismatched types like a number replaced by an object), it falls back to a value comparison via JSON.stringify equality and tags the result changed with both the old and new value preserved, or same if they're identical.

A diff tree, not a flat list

Every recursive call returns a node describing its own status — added, removed, changed, same, or nested (an object/array that contains changes somewhere inside it) — plus, for objects and arrays, a children/items structure holding the same kind of node for every key or index. renderDiff() then walks that tree and prints indented, properly bracketed JSON, coloring each line by its node's status: green + for added, red - for removed, yellow ~ with an old → new pair for changed, and neutral gray for unchanged context — so nested changes stay visually anchored inside their real object/array structure instead of flattening into an unreadable list.

Handles nesting correctly, at any depth

Because diffValue calls itself for every object/array value, a change three levels deep inside a nested settings object is caught and reported at exactly that depth, with every ancestor object correctly marked nested so it still renders (unchanged siblings included) rather than being collapsed away. Pair this with a code diff viewer for comparing raw text, or a JSON tree viewer for exploring a single document.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSTwo sample JSON documents load and diff immediately.
  2. 2
    Edit the "Before" JSONThe diff recomputes live, debounced by 200ms.
  3. 3
    Edit the "After" JSONAdded, removed, and changed keys highlight instantly.
  4. 4
    Read the output+ green additions, - red removals, ~ yellow old → new changes.
  5. 5
    Try nested changesEdit a value inside settings to see depth-correct diffing.
  6. 6
    Break the JSON syntaxA parse error names exactly which side and why.

Real-world uses

Common Use Cases

API response debugging
Compare two payloads to spot an unexpected change, ignoring whitespace and key order that make text diffs misleading.
Configuration and flag review
See exactly what changed between two config versions, with every value tagged added, removed or changed at its precise path.
Code review companions
Pair with a code diff viewer so reviewers can read structured data changes next to line-based source changes.
Data migration QA
Verify that a transform changed only the intended fields, showing old and new values side by side for changed primitives.
Schema documentation
Pair with a JSON tree viewer for exploring documents, since both walk real object structure rather than text lines.
Related: Live Log Stream Panel
See the Live Log Stream Panel for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A text diff compares two strings line by line, so reordering keys, changing indentation, or reformatting whitespace produces spurious differences even when the underlying data is identical. This tool instead parses both inputs with JSON.parse and recursively compares the resulting JavaScript objects and arrays by key and index, so only genuine structural differences — a key that's actually missing, added, or holds a different value — are reported.

diffValue(a, b) checks the types of both values. For two plain objects, it unions and sorts their key sets, then recursively calls diffValue on every shared key (tagging keys only in one side as added or removed). For two arrays, it walks index by index the same way. For anything else — primitives or mismatched types — it compares via JSON.stringify equality and returns either "same" or "changed" with both the old and new value attached. Every recursive call returns a small "node" describing its own status, which the renderer later walks to produce the formatted output.

Yes — because diffValue calls itself for every object or array value it encounters, a change buried inside a deeply nested object is detected and reported at its exact location, while every ancestor object along the way is marked "nested" (meaning it contains a change somewhere inside) so it still renders fully, including its unchanged sibling keys, rather than being flattened or collapsed.

diffValue only recurses when both sides are objects, or both sides are arrays. If the types don't match that way — for example a key that was an object in "Before" and a plain number in "After" — it falls through to the primitive comparison path, which correctly reports the whole value as "changed" with the old object and new primitive both shown, rather than trying to diff incompatible structures key by key.

The diffValue and renderDiff functions have no DOM dependency until the final render step, so you can keep them as-is and swap only the rendering: instead of building DOM line elements directly, walk the same returned diff tree recursively in your framework's template syntax, mapping each node's status to a CSS class the same way. Keep the JSON.parse calls in a try/catch tied to your input state so parse errors surface the same way.