JSON diff & patch library with support for objects, arrays, text diffs, and multiple output formats
npx @tessl/cli install tessl/npm-jsondiffpatch@0.7.0jsondiffpatch is a comprehensive JSON diff & patch library that detects differences between JavaScript objects and arrays, generates change deltas, and applies patches to transform objects from one state to another. It supports intelligent array diffing using LCS algorithm, multiple output formats, text diffing for strings, and extensive customization options.
npm install jsondiffpatchimport { create, diff, patch, unpatch, reverse, clone, DiffPatcher } from "jsondiffpatch";For text diff support:
import { create, diff, patch, unpatch, reverse, clone, DiffPatcher } from "jsondiffpatch/with-text-diffs";CommonJS:
const { create, diff, patch, unpatch, reverse, clone, DiffPatcher } = require("jsondiffpatch");import { diff, patch, reverse } from "jsondiffpatch";
// Generate diff between two objects
const left = { name: "Alice", age: 25, active: true };
const right = { name: "Alice", age: 26, active: false, role: "admin" };
const delta = diff(left, right);
console.log(delta);
// { age: [25, 26], active: [true, false], role: ["admin"] }
// Apply patch to get target object
const result = patch(left, delta);
console.log(result);
// { name: "Alice", age: 26, active: false, role: "admin" }
// Reverse a patch
const reverseDelta = reverse(delta);
const original = patch(result, reverseDelta);
console.log(original);
// { name: "Alice", age: 25, active: true }jsondiffpatch is built around several key components:
Primary diff and patch operations for generating and applying changes between JavaScript values.
function diff(left: unknown, right: unknown): Delta;
function patch(left: unknown, delta: Delta): unknown;
function unpatch(right: unknown, delta: Delta): unknown;
function reverse(delta: Delta): Delta;
function clone(value: unknown): unknown;
function create(options?: Options): DiffPatcher;Object-oriented interface providing full control over diff and patch operations with customizable options.
class DiffPatcher {
constructor(options?: Options);
diff(left: unknown, right: unknown): Delta;
patch(left: unknown, delta: Delta): unknown;
unpatch(right: unknown, delta: Delta): unknown;
reverse(delta: Delta): Delta;
clone(value: unknown): unknown;
options(options?: Options): Options;
}Multiple output formats for visualizing and processing diffs including console output, HTML visualization, JSON Patch compatibility, and annotated formatting.
// Console formatter
import * as console from "jsondiffpatch/formatters/console";
function format(delta: Delta, left?: unknown): string;
function log(delta: Delta, left?: unknown): void;
// HTML formatter
import * as html from "jsondiffpatch/formatters/html";
function format(delta: Delta, left?: unknown): string;
function showUnchanged(show?: boolean, node?: Element, delay?: number): void;
function hideUnchanged(node?: Element, delay?: number): void;
// JSON Patch formatter
import * as jsonpatch from "jsondiffpatch/formatters/jsonpatch";
function format(delta: Delta): Op[];
function patch(target: unknown, operations: Op[]): void;
// Annotated formatter
import * as annotated from "jsondiffpatch/formatters/annotated";
function format(delta: Delta, left?: unknown): string;Comprehensive configuration system for customizing diff behavior including array handling, text diffing, property filtering, and output formatting.
interface Options {
objectHash?: (item: object, index?: number) => string | undefined;
matchByPosition?: boolean;
arrays?: {
detectMove?: boolean;
includeValueOnMove?: boolean;
};
textDiff?: {
diffMatchPatch: typeof diff_match_patch;
minLength?: number;
};
propertyFilter?: (name: string, context: DiffContext) => boolean;
cloneDiffValues?: boolean | ((value: unknown) => unknown);
omitRemovedValues?: boolean;
}jsondiffpatch uses structured delta objects to represent different types of changes:
type Delta = AddedDelta | ModifiedDelta | DeletedDelta | ObjectDelta | ArrayDelta | MovedDelta | TextDiffDelta | undefined;
// Basic change types
type AddedDelta = [unknown]; // [newValue]
type ModifiedDelta = [unknown, unknown]; // [oldValue, newValue]
type DeletedDelta = [unknown, 0, 0]; // [oldValue, 0, 0]
// Complex change types
type MovedDelta = [unknown, number, 3]; // [value, newIndex, 3]
type TextDiffDelta = [string, 0, 2]; // [diffString, 0, 2]
interface ObjectDelta { [property: string]: Delta; }
interface ArrayDelta { _t: "a"; [index: number | `${number}`]: Delta; }function isAddedDelta(delta: Delta): delta is AddedDelta;
function isModifiedDelta(delta: Delta): delta is ModifiedDelta;
function isDeletedDelta(delta: Delta): delta is DeletedDelta;
function isObjectDelta(delta: Delta): delta is ObjectDelta;
function isArrayDelta(delta: Delta): delta is ArrayDelta;
function isMovedDelta(delta: Delta): delta is MovedDelta;
function isTextDiffDelta(delta: Delta): delta is TextDiffDelta;Context objects provide state and configuration for diff, patch, and reverse operations. These are primarily used when creating custom filters or advanced pipeline customization.
// Base context class
abstract class Context<TResult> {
abstract pipe: string;
result?: TResult;
hasResult?: boolean;
exiting?: boolean;
parent?: Context<TResult>;
childName?: string | number;
root?: Context<TResult>;
options?: Options;
children?: Context<TResult>[];
nextAfterChildren?: Context<TResult> | null;
next?: Context<TResult> | null;
setResult(result: TResult): this;
exit(): this;
push(child: Context<TResult>, name?: string | number): this;
}
// Diff operation context
class DiffContext extends Context<Delta> {
pipe: "diff";
left: unknown;
right: unknown;
leftType?: string;
rightType?: string;
leftIsArray?: boolean;
rightIsArray?: boolean;
constructor(left: unknown, right: unknown);
prepareDeltaResult<T extends Delta>(result: T): T;
setResult(result: Delta): this;
}
// Patch operation context
class PatchContext extends Context<unknown> {
pipe: "patch";
left: unknown;
delta: Delta;
constructor(left: unknown, delta: Delta);
}
// Reverse operation context
class ReverseContext extends Context<Delta> {
pipe: "reverse";
delta: Delta;
constructor(delta: Delta);
}Usage in Custom Filters:
import { DiffContext, PatchContext, ReverseContext } from "jsondiffpatch";
// Example custom filter
const customFilter = (context) => {
if (context.pipe === "diff") {
const diffCtx = context; // DiffContext
console.log(`Diffing: ${typeof diffCtx.left} -> ${typeof diffCtx.right}`);
// Access parent context for nested operations
if (diffCtx.parent) {
console.log(`Parent operation: ${diffCtx.parent.pipe}`);
}
// Set custom result
if (diffCtx.left === diffCtx.right) {
diffCtx.setResult(undefined); // No difference
diffCtx.exit(); // Skip further processing
}
}
};
// Context hierarchy for nested objects
const result = diff({ user: { name: "Alice" } }, { user: { name: "Bob" } });
// Creates nested DiffContext instances:
// - Root context for entire object
// - Child context for 'user' property
// - Child context for 'name' property/**
* JSON.parse reviver function for converting ISO date strings back to Date objects
* Handles ISO 8601 format: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss±HH:mm
* @param key - Property key (unused)
* @param value - Property value to potentially revive
* @returns Date object if value matches ISO date pattern, otherwise original value
*/
function dateReviver(key: string, value: unknown): unknown;
/**
* Create deep clone of any value using the default DiffPatcher instance
* @param value - Value to clone
* @returns Deep cloned copy
*/
function clone(value: unknown): unknown;Date Reviver Usage:
import { dateReviver } from "jsondiffpatch";
// Parse JSON with automatic date revival
const jsonString = '{"created": "2023-12-25T10:30:00.000Z", "name": "example"}';
const parsed = JSON.parse(jsonString, dateReviver);
console.log(parsed.created instanceof Date); // true
console.log(parsed.created.getFullYear()); // 2023
console.log(parsed.name); // "example" (unchanged)
// Handles various ISO 8601 formats
const formats = [
"2023-12-25T10:30:00Z", // UTC
"2023-12-25T10:30:00.123Z", // UTC with milliseconds
"2023-12-25T10:30:00+05:30", // Timezone offset
"2023-12-25T10:30:00-08:00" // Negative timezone
];
formats.forEach(dateStr => {
const result = dateReviver("", dateStr);
console.log(result instanceof Date); // true for all
});jsondiffpatch includes a command-line interface for diffing JSON files with support for HTTP URLs and multiple output formats:
jsondiffpatch left.json right.json [options]Options:
--format=console|json|json-compact|jsonpatch - Output format (default: console)--omit-removed-values - Exclude removed values from output (makes diffs irreversible)--no-moves - Disable array move detection (improves performance on large arrays)--no-text-diff - Disable text diffing for long strings--object-keys=prop1,prop2 - Object matching keys for arrays (default: id,key)--help - Show usage information with example outputExit Codes:
0 - No differences found between files1 - Differences found (standard for diff tools)2 - Error occurred (invalid arguments, file not found, etc.)Input Sources:
file1.json file2.jsonlocal.json https://api.example.com/dataUsage Examples:
# Basic diff with colorized console output
jsondiffpatch file1.json file2.json
# Raw JSON delta output
jsondiffpatch file1.json file2.json --format=json
# Compact JSON (single line)
jsondiffpatch file1.json file2.json --format=json-compact
# RFC 6902 JSON Patch format
jsondiffpatch file1.json file2.json --format=jsonpatch
# Disable move detection for performance
jsondiffpatch large1.json large2.json --no-moves
# Compare remote APIs
jsondiffpatch https://api.example.com/v1 https://api.example.com/v2
# Object matching by specific keys (for array elements)
jsondiffpatch users1.json users2.json --object-keys=id,email
# Omit old values (smaller output, cannot reverse)
jsondiffpatch old.json new.json --omit-removed-values
# Help with live demo
jsondiffpatch --helpDefault Behavior:
jsondiffpatch/with-text-diffs)id or key properties first, then by position