@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.

ParameterTypeDescription
baseJSONunknownThe base JSON value (the side considered the original).
contrastJSONunknownThe JSON value compared against the base.
optionsCompareOptionsOptional 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

OptionTypeDefaultDescription
arrayCompareMethodArrayCompareMethod'byIndex'Strategy used to compare arrays.
keyCaseInsensitivebooleanfalseWhen true, object keys are compared case-insensitively.
valueCaseInsensitivebooleanfalseWhen true, string values are compared case-insensitively.
numericStringEqualsNumberbooleanfalseWhen true, numeric strings are treated as equal to their numeric counterparts (e.g. "1" equals 1).

ArrayCompareMethod

type ArrayCompareMethod = 'byIndex' | 'lcs' | 'unordered';
ValueDescription
'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';
ValueDescription
'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. numberstring).
'valueChanged'The value changed while the type stayed the same.

JSONValueDifference

FieldTypeDescription
pathSegmentsstring[]Path to the differing value as segments. Object keys appear as-is; array indices appear as '[n]' (e.g. ['users', '[0]', 'name']).
pathStringstringSame 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.
diffTypeJSONValueDiffTypeKind of difference detected at this path.

License

MIT