JSON structural diff with colorized output and fuzzy array matching
npx @tessl/cli install tessl/npm-json-diff@1.0.0JSON Diff provides structural comparison of JSON objects and files with colorized, diff-like output. It features fuzzy matching of modified array elements, extensive configuration options, and both programmatic and command-line interfaces.
npm install json-diffnpm install -g json-diffconst { diff, diffString, colorize, colorizeToCallback } = require('json-diff');ES6 module import:
import { diff, diffString, colorize, colorizeToCallback } from 'json-diff';Additional exports (from colorize module):
const { colorizeToArray, colorizeToCallback } = require('json-diff/lib/colorize');
// or
import { colorizeToArray, colorizeToCallback } from 'json-diff/lib/colorize';Note: colorize and colorizeToCallback are available from both the main module and the colorize module, but colorizeToArray is only available from the colorize module.
const { diff, diffString } = require('json-diff');
// Basic comparison
const obj1 = { foo: 'bar', num: 1 };
const obj2 = { foo: 'baz', num: 1 };
// Get raw diff result
const diffResult = diff(obj1, obj2);
console.log(diffResult);
// Output: { foo: { __old: 'bar', __new: 'baz' } }
// Get colorized string output
const diffOutput = diffString(obj1, obj2);
console.log(diffOutput);
// Output:
// {
// - foo: "bar"
// + foo: "baz"
// }
// Array comparison with fuzzy matching
const arr1 = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const arr2 = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Robert' }];
console.log(diffString(arr1, arr2));# Compare two JSON files
json-diff file1.json file2.json
# With options
json-diff --full --color file1.json file2.json
json-diff --raw-json --keys-only file1.json file2.jsonCompares two JSON objects and returns the structural differences.
/**
* Compare two JSON objects and return structural differences
* @param {any} obj1 - First object to compare
* @param {any} obj2 - Second object to compare
* @param {DiffOptions} options - Configuration options
* @returns {any|undefined} Difference object or undefined if objects are equal
*/
function diff(obj1, obj2, options = {});
interface DiffOptions {
/** Array of keys to always include in output even if unchanged */
outputKeys?: string[];
/** Array of keys to exclude from comparison */
excludeKeys?: string[];
/** Include equal sections of the document, not just deltas */
full?: boolean;
/** Compare only the keys, ignore the differences in values */
keysOnly?: boolean;
/** Include unchanged values in output instead of omitting them */
keepUnchangedValues?: boolean;
/** Output only the updated and new key/value pairs */
outputNewOnly?: boolean;
/** Sort primitive values in arrays before comparing */
sort?: boolean;
/** Round floating point numbers to specified decimal places before comparison */
precision?: number;
}Examples:
// Basic comparison
diff({ a: 1, b: 2 }, { a: 1, b: 3 });
// Returns: { b: { __old: 2, __new: 3 } }
// Full mode - includes unchanged values
diff({ a: 1, b: 2 }, { a: 1, b: 3 }, { full: true });
// Returns: { a: 1, b: { __old: 2, __new: 3 } }
// Keys only mode
diff({ a: 1, b: 2 }, { a: 1, b: 3 }, { keysOnly: true });
// Returns: undefined (same keys)
// Exclude specific keys
diff({ a: 1, b: 2, c: 3 }, { a: 1, b: 3, c: 4 }, { excludeKeys: ['c'] });
// Returns: { b: { __old: 2, __new: 3 } }
// Always output certain keys
diff({ a: 1, b: 2 }, { a: 1, b: 2 }, { outputKeys: ['a'] });
// Returns: { a: 1 }
// Precision rounding
diff({ pi: 3.14159 }, { pi: 3.14158 }, { precision: 3 });
// Returns: undefined (rounded values are equal)Compares two JSON objects and returns a colorized string representation of the differences.
/**
* Compare two JSON objects and return colorized string output
* @param {any} obj1 - First object to compare
* @param {any} obj2 - Second object to compare
* @param {DiffStringOptions} options - Display and comparison options
* @returns {string} Formatted, colorized string representation of differences
*/
function diffString(obj1, obj2, options = {});
interface DiffStringOptions extends DiffOptions {
/** Enable/disable colored output (default: true) */
color?: boolean;
/** Custom color theme object */
theme?: ColorTheme;
/** Maximum number of elisions to show before collapsing them */
maxElisions?: number;
}
interface ColorTheme {
' '?: (text: string) => string; // Unchanged content (default: identity function)
'+'?: (text: string) => string; // Added content (default: green)
'-'?: (text: string) => string; // Removed content (default: red)
}Examples:
// Colorized output (default)
diffString({ foo: 'bar' }, { foo: 'baz' });
// No colors
diffString({ foo: 'bar' }, { foo: 'baz' }, { color: false });
// Custom max elisions (default: Infinity)
diffString(largeArray1, largeArray2, { maxElisions: 3 });
// If more than 3 consecutive unchanged items, shows "... (N entries)" instead of individual "..."
// Full mode with no elisions limit
diffString(obj1, obj2, { full: true, maxElisions: Infinity });Converts a diff result object into a formatted, colorized string.
/**
* Convert diff result to colorized string output
* @param {any} diff - Diff result object from diff() function
* @param {ColorizeOptions} options - Display options
* @returns {string} Formatted, colorized string
*/
function colorize(diff, options = {});
interface ColorizeOptions {
/** Enable/disable colored output (default: true) */
color?: boolean;
/** Custom color theme object */
theme?: ColorTheme;
/** Maximum number of elisions to show before collapsing them */
maxElisions?: number;
}Example:
const diffResult = diff({ foo: 'bar' }, { foo: 'baz' });
const colorized = colorize(diffResult, { color: true });
console.log(colorized);Converts a diff result object to colorized output using a callback function for each line.
/**
* Convert diff result to colorized output using callback
* @param {any} diff - Diff result object
* @param {ColorizeOptions} options - Display options
* @param {ColorizeCallback} output - Callback function to handle output lines
* @returns {void}
*/
function colorizeToCallback(diff, options, output);
type ColorizeCallback = (color: string, line: string) => void;Example:
const lines = [];
const diffResult = diff({ foo: 'bar' }, { foo: 'baz' });
colorizeToCallback(diffResult, { color: false }, (color, line) => {
lines.push(`${color}${line}`);
});
console.log(lines.join('\n'));Converts a diff result object to an array of colorized strings (available directly from colorize module).
/**
* Convert diff result to array of colorized lines
* @param {any} diff - Diff result object from diff() function
* @param {ColorizeOptions} options - Display options (optional)
* @returns {string[]} Array of formatted, colorized strings
*/
function colorizeToArray(diff, options = {});Note: This function is exported from json-diff/lib/colorize but not from the main module.
Example:
const { colorizeToArray } = require('json-diff/lib/colorize');
const diffResult = diff({ foo: 'bar' }, { foo: 'baz' });
const lines = colorizeToArray(diffResult, { color: false });
console.log(lines);
// Output: [' {', '- foo: "bar"', '+ foo: "baz"', ' }']The json-diff command provides a command-line interface for comparing JSON files.
# Usage
json-diff [options] <first.json> <second.json>
# Arguments:
# <first.json> Old file (required)
# <second.json> New file (required)
# General Options:
# -v, --verbose Output progress info
# -C, --[no-]color Colored output (auto-detected from TTY)
# -j, --raw-json Display raw JSON encoding of the diff
# -f, --full Include equal sections of document, not just deltas
# --max-elisions COUNT Max number of ...s to show in a row in "deltas" mode (before collapsing them)
# Filtering Options:
# -o, --output-keys KEYS Always print comma-separated keys with their value, if they are part of an object with any diff
# -x, --exclude-keys KEYS Exclude comma-separated keys from comparison on both files
# -n, --output-new-only Output only updated and new key/value pairs (without marking them as such)
# Comparison Options:
# -s, --sort Sort primitive values in arrays before comparing
# -k, --keys-only Compare only the keys, ignore 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
# Exit Codes:
# 0 No differences found
# 1 Differences foundExamples:
# Basic comparison (colors auto-detected based on TTY)
json-diff a.json b.json
# Full output with explicit colors
json-diff --full --color a.json b.json
# Force no colors
json-diff --no-color a.json b.json
# Raw JSON output only
json-diff --raw-json a.json b.json
# Compare only structure, not values
json-diff --keys-only a.json b.json
# Exclude timestamps from comparison
json-diff --exclude-keys timestamp,updatedAt a.json b.json
# Always show id field even if unchanged (requires --full or changes in object)
json-diff --output-keys id a.json b.jsonWhen using the diff() function or --raw-json CLI flag:
{ "key": { "__old": oldValue, "__new": newValue } }{ "key__deleted": deletedValue }{ "key__added": newValue }[operation, value]" " (unchanged), "+" (added), "-" (deleted), "~" (modified)[" "] (value omitted)[" ", value]Examples:
// Object with changed value
diff({ name: "John" }, { name: "Jane" });
// Result: { name: { __old: "John", __new: "Jane" } }
// Object with added key
diff({ a: 1 }, { a: 1, b: 2 });
// Result: { b__added: 2 }
// Array with changes
diff([1, 2, 3], [1, 4, 3]);
// Result: [[" "], ["-", 2], ["+", 4], [" "]]When using diffString() or CLI without --raw-json:
...JSON Diff uses intelligent fuzzy matching for arrays containing objects. When array elements are modified, it attempts to match similar objects rather than treating them as separate additions and deletions.
const before = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" }
];
const after = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Robert", role: "user" } // name changed
];
// Shows modification rather than delete + add
diffString(before, after);
// Output shows Bob -> Robert change, not full object replacementFor floating-point comparisons, use the precision option to avoid issues with floating-point representation:
diff(
{ measurement: 3.14159265359 },
{ measurement: 3.14159265358 },
{ precision: 5 }
);
// Returns: undefined (equal when rounded to 5 decimal places)The library throws errors for:
All functions handle null, undefined, and various JavaScript types gracefully.
// Core diff result types
type DiffResult = any | undefined;
// Array diff tuple format
type ArrayDiffTuple = [' ' | '+' | '-' | '~', any] | [' '];
// Object change format for scalar values
interface ScalarChange {
__old: any;
__new: any;
}
// Object key change formats
interface ObjectKeyChange {
[key: `${string}__added`]: any; // Added keys
[key: `${string}__deleted`]: any; // Deleted keys
}
// Color operations used in diff output
type ColorOperation = ' ' | '+' | '-' | '~';
// CLI exit codes
const CLI_EXIT_CODES = {
NO_DIFFERENCES: 0, // Files are identical
DIFFERENCES_FOUND: 1 // Files have differences
};
// Extended types recognized by json-diff
type ExtendedType = 'null' | 'array' | 'date' | 'object' | 'string' | 'number' | 'boolean' | 'undefined';