@compare-json/core (JavaScript)
@compare-json/core is a lightweight, dependency-free JavaScript/TypeScript library for deep comparison of JSON values — the same engine that powers this website. It detects additions, deletions, type changes, and value changes between two JSON structures, with fine-grained control over comparison behavior.
Language: JavaScript/TypeScript — distributed via npm. Implementations for other languages may follow.
Installation
# npm
npm install @compare-json/core
# yarn
yarn add @compare-json/core
# pnpm
pnpm add @compare-json/core
Quick Start
import { compareJSON } from '@compare-json/core';
const baseJSON = { name: 'Alice', age: 30, hobbies: ['reading'] };
const contrastJSON = { name: 'Bob', age: '30', hobbies: ['reading', 'coding'], email: '[email protected]' };
const differences = compareJSON({ baseJSON, contrastJSON });
console.log(differences);
// [
// { pathSegments: ['name'], pathString: 'name', pathBelongsTo: 'both', diffType: 'valueChanged' },
// { pathSegments: ['age'], pathString: 'age', pathBelongsTo: 'both', diffType: 'typeChanged' },
// { pathSegments: ['hobbies', '[1]'], pathString: 'hobbies[1]', pathBelongsTo: 'contrast', diffType: 'added' },
// { pathSegments: ['email'], pathString: 'email', pathBelongsTo: 'contrast', diffType: 'added' },
// ]
Compare Options
import { compareJSON } from '@compare-json/core';
// Treat numeric strings as equal to numbers
compareJSON({
baseJSON: { count: 1 },
contrastJSON: { count: '1' },
options: { numericStringEqualsNumber: true },
});
// [] (no differences)
// Case-insensitive key comparison
compareJSON({
baseJSON: { Name: 'Alice' },
contrastJSON: { name: 'Alice' },
options: { keyCaseInsensitive: true },
});
// [] (no differences)
// Case-insensitive value comparison
compareJSON({
baseJSON: { status: 'OK' },
contrastJSON: { status: 'ok' },
options: { valueCaseInsensitive: true },
});
// [] (no differences)
See Comparison Options for details on what each option does.
Array Comparison Methods
import { compareJSON } from '@compare-json/core';
// 'byIndex' (default) — compares elements at the same index
compareJSON({ baseJSON: [1, 2, 3], contrastJSON: [2, 3, 4] });
// 'lcs' — uses Longest Common Subsequence for minimal diff
compareJSON({
baseJSON: [1, 2, 3],
contrastJSON: [2, 3, 4],
options: { arrayCompareMethod: 'lcs' },
});
// 'unordered' — treats arrays as multisets, ignoring element order
compareJSON({
baseJSON: [1, 2, 3],
contrastJSON: [3, 2, 1],
options: { arrayCompareMethod: 'unordered' },
});
// [] (no differences)
See Array Comparison Methods for a deeper explanation of each strategy.
Formatting Paths
import { pathSegmentsToString } from '@compare-json/core';
pathSegmentsToString(['users', '[0]', 'name']);
// 'users[0].name'
API Reference
compareJSON
function compareJSON(params: {
baseJSON: unknown;
contrastJSON: unknown;
options?: CompareOptions;
}): JSONValueDifference[];
Deeply compares two JSON values and returns an array of differences. Returns an empty array when the values are equal.
| Parameter | Type | Description |
|---|---|---|
baseJSON | unknown | The base JSON value (the side considered the original). |
contrastJSON | unknown | The JSON value compared against the base. |
options | CompareOptions | Optional settings to customize comparison behavior. |
pathSegmentsToString
function pathSegmentsToString(pathSegments: string[]): string;
Converts a path segment array (as found in JSONValueDifference.pathSegments) into a human-readable dot-notation string. Array index segments (e.g. '[0]') are appended without a leading dot; object key segments are joined with ..
pathSegmentsToString([]); // ''
pathSegmentsToString(['user', 'name']); // 'user.name'
pathSegmentsToString(['items', '[2]', 'id']); // 'items[2].id'
CompareOptions
| Option | Type | Default | Description |
|---|---|---|---|
arrayCompareMethod | ArrayCompareMethod | 'byIndex' | Strategy used to compare arrays. |
keyCaseInsensitive | boolean | false | When true, object keys are compared case-insensitively. |
valueCaseInsensitive | boolean | false | When true, string values are compared case-insensitively. |
numericStringEqualsNumber | boolean | false | When true, numeric strings are treated as equal to their numeric counterparts (e.g. "1" equals 1). |
ArrayCompareMethod
type ArrayCompareMethod = 'byIndex' | 'lcs' | 'unordered';
| Value | Description |
|---|---|
'byIndex' | Compares array elements pairwise at the same index. Extra trailing elements are reported as added/deleted. |
'lcs' | Uses the Longest Common Subsequence algorithm for minimal-diff detection in ordered arrays. |
'unordered' | Treats arrays as multisets, matching equal elements regardless of position. |
JSONValueDiffType
type JSONValueDiffType = 'added' | 'deleted' | 'typeChanged' | 'valueChanged';
| Value | Description |
|---|---|
'added' | Value exists in contrastJSON but not in baseJSON. |
'deleted' | Value exists in baseJSON but not in contrastJSON. |
'typeChanged' | The value type changed between base and contrast (e.g. number → string). |
'valueChanged' | The value changed while the type stayed the same. |
JSONValueDifference
| Field | Type | Description |
|---|---|---|
pathSegments | string[] | Path to the differing value as segments. Object keys appear as-is; array indices appear as '[n]' (e.g. ['users', '[0]', 'name']). |
pathString | string | Same path joined into dot-notation, with array indices kept as bracket suffixes (e.g. 'users[0].name'). |
pathBelongsTo | 'base' | 'contrast' | 'both' | Side that owns the path. 'base' for deleted, 'contrast' for added, 'both' for valueChanged and typeChanged. |
diffType | JSONValueDiffType | Kind of difference detected at this path. |
License
MIT