Skip to content

Repository files navigation

JSON structural diff

Does exactly what you think it does:

Screenshot

Notice: This is a modernized, actively maintained fork of the original json-diff project.

Why this fork?

The original repository has accumulated unaddressed bugs and lacks recent updates. This fork was created to provide the community with an active release cycle for critical bug fixes and architectural modernizations.

Key Improvements:

  • Fully Modernized Engine: Re-architected code structure using modern JavaScript standards.
  • Standalone Component: Re-architected the codebase to expose JsonDiff as a standalone component, allowing it to be used both as a programmatic library and via the CLI.
  • Added a Debug Option: The debug feature ouputs a pivot table that displays the fuzzy scores that are computed when diffing arrays. When combined with the -v flag, the raw data that the pivot table is derived from is displayed as well. This is an incredibly useful feature for understanding how the diffing algorithm works.
  • Bug Fixes: Resolves critical long-standing issues, similar to those documented in the open PR [fix][102]Fix deep diffing issue for obj in array, which addresses andreyvit/json-diff#102.
  • Playground: New feature that facilitates learning, experimentation and debugging.

Installation

Replace the original tool by installing this package globally:

bun add -g @jedluhmann/json-diff

Usage

Once installed, you can use it exactly like the original tool:

json-diff file1.json file2.json

(Note: Depending on your system configuration, you can also execute it via bunx @your-username/json-diff file1.json file2.json without installing it globally).

Contribution policy

  1. This project is maintained thanks to your contributions! Please send pull requests.

  2. I will merge any pull request that adds something useful, does not break existing things, has reasonable code quality and provides/updates tests where appropriate.

CLI Usage

Simple:

    json-diff a.json b.json

Detailed:

    % json-diff --help

    Usage: json-diff [-vCjfonskKp] first.json second.json

    Arguments:
    <first.json>          Old file
    <second.json>         New file

    General options:
    -v, --verbose         Output progress info
    -C, --[no-]color      Colored output
    -j, --raw-json        Display raw JSON encoding of the diff
    -f, --full            Include the equal sections of the document, not just the deltas
        --max-elisions COUNT  Max number of ...s to show in a row in "deltas" mode (before
                                collapsing them)

    -o, --output-keys KEYS  Always print this comma separated keys, with their value, if they are
                            part of an object with any diff

    -x, --exclude-keys KEYS  Exclude these comma separated keys from comparison on both files

    -n, --output-new-only   Output only the updated and new key/value pairs (without marking them as
                            such). If you need only the diffs from the old file, just exchange the
                            first and second json.

    -s, --sort            Sort primitive values in arrays before comparing
    -k, --keys-only       Compare only the keys, ignore the differences in values
    -K, --keep-unchanged-values   Instead of omitting values that are equal, output them as they are
    -p, --precision DECIMALS  Round all floating point numbers to this number of decimal places prior
                              to comparison
    -d, --debug             Output fuzzy match details for array diffs, can be combined with -v for
                            additional info #var(debug)

    -h, --help            Display this usage information

JavaScript Usage:

In addition to the CLI, json-diff can also be included in your javascript applications.

import { JsonDiff } from '@jedluhmann/json-diff';
let options = {},
  result;
const jsonDiff = new JsonDiff(options);

console.log(`\nawait jsonDiff.exec({ foo: 'bar' }, { foo: 'baz' });`);
result = await jsonDiff.exec({ foo: 'bar' }, { foo: 'baz' });

// As above, but without console colors
jsonDiff.options = { color: false };
console.log(`\n\nSame as before, but without color`);
result = await jsonDiff.exec({ foo: 'bar' }, { foo: 'baz' });

// Raw JSON output option:
jsonDiff.options = { raw: true };
console.log(`\n\nRaw JSON Output`);
result = await jsonDiff.exec({ foo: 'bar', b: 3 }, { foo: 'baz', b: 3 });

// Raw JSON output option together with the "full" option:
jsonDiff.options = { raw: true, full: true };
console.log(`\n\nRaw JSON Output together with the "full" option`);
result = await jsonDiff.exec({ foo: 'bar', b: 3 }, { foo: 'baz', b: 3 });

Output from above:

result = await jsonDiff.exec({ foo: 'bar' }, { foo: 'baz' });
 {
-  foo: "bar"
+  foo: "baz"
 }


Same as before, but without color
 {
-  foo: "bar"
+  foo: "baz"
 }


Raw JSON Output Option
{
  "foo": {
    "__old": "bar",
    "__new": "baz"
  }
}


Raw JSON Output Option together with the "full" option
{
  "foo": {
    "__old": "bar",
    "__new": "baz"
  },
  "b": 3
}

Programmatic Access

When creating scripts to automate json-diff, you can silence the output by setting jsonDiff.options = { silent: true, debug: false }.

You can also replace the call to exec() with diff(). The only difference is that jsonDiff.diff() is synchronous and the return value is a diff object with the properties: score, result, and equal.

Heres a quick example:

import { JsonDiff } from '@jedluhmann/json-diff';
let options = {},
  result;
const jsonDiff = new JsonDiff(options);

let objA = { foo: 'bar' };
let objB = { foo: 'baz' };

let diff = jd.diff(objA, objB);
let { score, result, equal } = diff;
console.log(`score: ${score}, result: ${JSON.stringify(result)}, equal: ${equal}`);

Output:

score: 0, result: {"foo":{"__old":"bar","__new":"baz"}}, equal: false

Playground

The script, playground/jd-debug.ts, offers a convenient way to experiment with json-diff and provides some helpful examples to get you started. When this script has focus in VS Code, you can debug json-diff via the "Debug File" launch config. Simply set your break points and click the debug button.

Features

  • colorized, diff-like output
  • fuzzy matching of modified array elements (when array elements are object hierarchies) along with a debug option for displaying the fuzzy matches pivot table
  • "keysOnly" option to compare only the json structure (keys), ignoring the values
  • "full" option to output the entire json tree, not just the deltas
  • "outputKeys" option to always output the given keys for an object that has differences
  • reasonable test coverage (far from 100%, though)

Conceptual Overview (Core Algorithm)

                  exec(old, new)
                        |
                        |
    ┌──────────>  diff(old, new) <──────────┐
    │                   │                   │
    │         ┌─────────┴─────────┐         │
    │         │                   │         │
    │      objects             arrays       │
    │         │                   │         │
    │   compare keys       match elements   │
    │         │                   │         │
    └─── recursively         recursively ───┘
              │                   │
              └─────────┬─────────┘
                        │
                     scalars
                        │
                  compare values

Raw JSON Output Mode

CLI option: -j or --raw-json

The Raw JSON mode outputs the return result of the diff(), as opposed to the standard red(-)/green(+) output like you see in a visual diff tool.

This is useful for gaining a better understanding of how json-diff works as well as for programmatic use. Adding the --full option, includes all values (not just the differences). The examples below show return values for simple arrays and objects, but note that arrays and objects can be nested in one and another, in which case you will see a combination of the two.

ARRAYS

Unless two arrays are equal, all array elements are transformed into 2-tuple arrays:

  • The first element is a one character string denoting the equality ('+', '-', '~', ' ')
  • The second element is the old (-), new (+), altered sub-object (~), or unchanged (' ') value
    json-diff --full --raw-json <(echo '[1,7,3]') <(echo '[1,2,3]')
         [ [ " ", 1 ], [ "-", 7 ], [ "+", 2 ], [ " ", 3 ] ]
    json-diff --full --raw-json <(echo '[1,["a","b"],4]') <(echo '[1,["a","c"],4]')
         [ [ " ", 1 ], [ "~", [ [ " ", "a" ], [ "-", "b" ], [ "+", "c" ] ] ], [ " ", 4 ] ]
  • If two arrays are equal, they are left as is.

OBJECTS

Object property values:

  • If equal, they are left as is
  • Unequal scalar values are replaced by an object containing the old and new value:
    json-diff --full  --raw-json <(echo '{"a":4}') <(echo '{"a":5}')
        { "a": { "__old": 4, "__new": 5 } }
  • Unequal arrays and objects are replaced by their diff:
    json-diff --full  --raw-json <(echo '{"a":[4,5]}') <(echo '{"a":[4,6]}')
        { "a": [ [ " ", 4 ], [ "-", 5 ], [ "+", 6 ] ] }

Object property keys:

  • Object keys that are deleted or added between two objects are marked as such:
    json-diff --full  --raw-json <(echo '{"a":[4,5]}') <(echo '{"b":[4,5]}')
        { "a__deleted": [ 4, 5 ], "b__added": [ 4, 5 ] }
    json-diff --full  --raw-json <(echo '{"a":[4,5]}') <(echo '{"b":[4,6]}')
        { "a__deleted": [ 4, 5 ], "b__added": [ 4, 6 ] }

Non-full mode

  • In regular, delta-only (non-"full") mode, equal properties and values are omitted:
    json-diff --raw-json <(echo '{"a":4, "b":6}') <(echo '{"a":5,"b":6}')
        { "a": { "__old": 4, "__new": 5 } }
  • Equal array elements are represented by a one-tuple containing only a space " ":
    json-diff --raw-json <(echo '[1,7,3]') <(echo '[1,2,3]')
        [ [ " " ], [ "-", 7 ], [ "+", 2 ], [ " " ] ]

Tests

Run:

  bun run test

Output:

Open to View Test Output 🔽
bun run test

$ mocha test/**/*.spec.js

colorizeToArray
  ✔ should return ' <value>' for a scalar value
  ✔ should return ' <value>' for 'null' value
  ✔ should return ' <value>' for 'false' value
  ✔ should return '-<old value>', '+<new value>' for a scalar diff
  ✔ should return '-<old value>', '+<new value>' for 'null' and 'false' diff
  ✔ should return '-<removed key>: <removed value>' for an object diff with a removed key
  ✔ should return '+<added key>: <added value>' for an object diff with an added key
  ✔ should return '+<added key>: <added value>' for an object diff with an added key with 'null' value
  ✔ should return '+<added key>: <added value>' for an object diff with an added key with 'false' value
  ✔ should return '+<added key>: <added stringified value>' for an object diff with an added key and a non-scalar value
  ✔ should return ' <modified key>: <colorized diff>' for an object diff with a modified key
  ✔ should return '+<inserted item>' for an array diff
  ✔ should return '-<deleted item>' for an array diff
  ✔ should handle an array diff with subobject diff
  ✔ should collapse long sequences of identical subobjects into one '...'

colorize
  ✔ should return a string with ANSI escapes
  ✔ should return a string without ANSI escapes on { color: false }

diff
  with simple scalar values
    ✔ should return undefined for two identical numbers
    ✔ should return undefined for two identical strings
    ✔ should return undefined for two identical dates
    ✔ should return { __old: <old value>, __new: <new value> } object for two different numbers
    ✔ should return { __old: <old value>, __new: <new value> } object for two different dates
  with objects
    ✔ should return undefined for two empty objects
    ✔ should return undefined for two objects with identical contents
    ✔ should return undefined for two object hierarchies with identical contents
    ✔ should return { <key>__deleted: <old value> } when the second object is missing a key
    ✔ should return { <key>__added: <new value> } when the first object is missing a key
    ✔ should return { <key>: { __old: <old value>, __new: <new value> } } for two objects with different scalar values for a key
    ✔ should return { <key>: <diff> } with a recursive diff for two objects with different values for a key
  with arrays of scalars
    ✔ should return undefined for two arrays with identical contents
    ✔ should return [..., ['-', <removed item>], ...] for two arrays when the second array is missing a value
    ✔ should return [..., ['+', <added item>], ...] for two arrays when the second one has an extra value
    ✔ should return [..., ['+', <added item>]] for two arrays when the second one has an extra value at the end (edge case test)
    ✔ should return [['-', true], ['+', 'true']] for two arrays with identical strings of different types
  with arrays of objects
    ✔ should return undefined for two arrays with identical contents
    ✔ should return undefined for two arrays with identical, empty object contents
    ✔ should return undefined for two arrays with identical, empty array contents
    ✔ should return undefined for two arrays with identical array contents including 'null'
    ✔ should return undefined for two arrays with identical, repeated contents
    ✔ should return [..., ['-', <removed item>], ...] for two arrays when the second array is missing a value
    ✔ should return [..., ['+', <added item>], ...] for two arrays when the second array has an extra value
    ✔ should return [['+', <added item>], ..., ['+', <added item>]] for two arrays containing objects of 3 or more properties when the second array has extra values (fixes issue #57)
    ✔ should return [..., ['+', <added item>], ...] for two arrays when the second array has a new but nearly identical object added
    ✔ should return [..., ['~', <diff>], ...] for two arrays when an item has been modified
    ✔ should correctly pick best match based on similarity during scalarize, i.e. class obj2[2] should not be selected as best match for obj1[0], instead order should remain unchanged with added class property for obj1[0]
    ✔ should return [[' ', <unchanged item>], ['~', <diff>], [' ', <unchanged item>]] for two arrays when an item has been modified
  with reported bugs
    ✔ should handle type mismatch during scalarize
    ✔ should handle mixed scalars and non-scalars in scalarize

diff({sort: true})
  with arrays
    ✔ should return undefined for two arrays with the same contents in different order

diff({keepUnchangedValues: true})
  with nested object
    ✔ should return partial object with modified and unmodified elements in the edited scope

diff({full: true})
  with simple scalar values
    ✔ should return the number for two identical numbers
    ✔ should return the string for two identical strings
    ✔ should return { __old: <old value>, __new: <new value> } object for two different numbers
  with objects
    ✔ should return an empty object for two empty objects
    ✔ should return the object for two objects with identical contents
    ✔ should return the object for two object hierarchies with identical contents
    ✔ should return { <key>__deleted: <old value>, <remaining properties>} when the second object is missing a key
    ✔ should return { <key>__added: <new value>, <remaining properties> } when the first object is missing a key
    ✔ should return { <key>: { __old: <old value>, __new: <new value> } } for two objects with different scalar values for a key
    ✔ should return { <key>: <diff>, <equal properties> } with a recursive diff for two objects with different values for a key
    ✔ should return { <key>: <diff>, <equal properties> } with a recursive diff for two objects with different values for a key
  with arrays of scalars
    ✔ should return an array showing no changes for any element for two arrays with identical contents
    ✔ should return [[' ', <unchanged item>], ['-', <removed item>], [' ', <unchanged item>]] for two arrays when the second array is missing a value
    ✔ should return [' ', <unchanged item>], ['+', <added item>], [' ', <unchanged item>]] for two arrays when the second one has an extra value
    ✔ should return [' ', <unchanged item>s], ['+', <added item>]] for two arrays when the second one has an extra value at the end (edge case test)
  with arrays of objects
    ✔ should return an array of unchanged elements for two arrays with identical contents
    ✔ should return an array with an unchanged element for two arrays with identical, empty object contents
    ✔ should return an array with an unchanged element for two arrays with identical, empty array contents
    ✔ should return an array of unchanged elements for two arrays with identical array contents including 'null'
    ✔ should return an array of unchanged elements for two arrays with identical, repeated contents
    ✔ should return [[' ', <unchanged item>], ['-', <removed item>], [' ', <unchanged item>]] for two arrays when the second array is missing a value
    ✔ should return [[' ', <unchanged item>], ['+', <added item>], [' ', <unchanged item>]] for two arrays when the second array has an extra value
    ✔ should return [[' ', <unchanged item>], ['+', <added item>], [' ', <unchanged item>]] for two arrays when the second array has a new but nearly identical object added
    ✔ should return [[' ', <unchanged item>], ['~', <diff>], [' ', <unchanged item>]] for two arrays when an item has been modified

diff({ outputKeys: foo,bar }
  ✔ should return keys foo and bar although they have no changes
  ✔ should return keys foo (with addition) and bar (with no changes)
  ✔ should return keys foo and bar (with addition)
  ✔ should return nothing as the entire object is equal, no matter that show keys has some of them
  ✔ should return the keys of an entire object although it has no changes

diff({ excludeKeys: foo,bar }
  ✔ shouldn't return keys foo and bar even thou they have changes
  ✔ shouldn't return keys foo (with addition) and bar (with no changes)
  ✔ shouldn't return keys foo and bar (with addition)

diff({keysOnly: true})
  with simple scalar values
    ✔ should return undefined for two identical numbers
    ✔ should return undefined for two identical strings
    ✔ should return undefined object for two different numbers
  with objects
    ✔ should return undefined for two empty objects
    ✔ should return undefined for two objects with identical contents
    ✔ should return undefined for two object hierarchies with identical contents
    ✔ should return { <key>__deleted: <old value> } when the second object is missing a key
    ✔ should return { <key>__added: <new value> } when the first object is missing a key
    ✔ should return undefined for two objects with different scalar values for a key
    ✔ should return undefined with a recursive diff for two objects with different values for a key
    ✔ should return { <key>: <diff> } with a recursive diff when second object is missing a key and two objects with different values for a key
  with arrays of scalars
    ✔ should return undefined for two arrays with identical contents
    ✔ should return undefined for two arrays with when an item has been modified
    ✔ should return [..., ['-', <removed item>], ...] for two arrays when the second array is missing a value
    ✔ should return [..., ['+', <added item>], ...] for two arrays when the second one has an extra value
    ✔ should return [..., ['+', <added item>]] for two arrays when the second one has an extra value at the end (edge case test)
  with arrays of objects
    ✔ should return undefined for two arrays with identical contents
    ✔ should return undefined for two arrays with identical, empty object contents
    ✔ should return undefined for two arrays with identical, empty array contents
    ✔ should return undefined for two arrays with identical, repeated contents
    ✔ should return [..., ['-', <removed item>], ...] for two arrays when the second array is missing a value
    ✔ should return [..., ['+', <added item>], ...] for two arrays when the second array has an extra value
    ✔ should return undefined for two arrays when an item has been modified

diffString
  ✔ should produce the expected result for the example JSON files
  ✔ should produce the expected result for the example JSON files with precision set to 1
  ✔ should produce the expected colored result for the example JSON files
  ✔ return an empty string when no diff found

diff({ outputNewOnly: true }
  ✔ should return only new diffs (added)
  ✔ should return only new diffs (changed)
  ✔ should return only new diffs (deleted)
  ✔ should return only old diffs - exchanged first and second json (added)
  ✔ should return only old diffs - exchanged first and second json (changed)
  ✔ should return only old diffs - exchanged first and second json (deleted)

115 passing (32ms)

Change Log

  • 1.0.0 Forked from https://github.com/andreyvit/json-diff @ version 1.0.6
    • Optimizes and modernizes the codebase
    • Can now run json-diff as a self-contained class
    • Fixes bugs that occurred when diffing arrarys of objects
    • New debug feature that displays fuzzy match data
    • New playground feature that facilitates learning, experimentation and debugging.
  • 1.0.1 Added Husky and Commitlint and automated package deployment
  • 1.0.2 Deploy fix
  • 1.0.3 Deploy fix
  • 1.0.4 Deploy with bun
  • 1.0.5 Deploy fix - create temporary .npmrc
  • 1.0.6 Deploy fix
  • 1.0.7 Deploy fix
  • 1.0.8 Deploy fix - get rid of warning for Node.js 20 deprecation

License

Copyright © 2026 Jed Luhmann. Distributed under the MIT license.
Copyright © 2015 Andrey Tarantsov. Distributed under the MIT license.

About

Structural diff for JSON files

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages