Concordance JavaScript Value Utility
Concordance is a robust JavaScript utility library designed for deep comparison, human-readable formatting, detailed diffing, and consistent serialization of any JavaScript value. Currently stable at version 5.0.4, it maintains an active release cadence with frequent patch releases addressing minor issues and less frequent major versions that introduce breaking changes or significant feature enhancements. A key differentiator is its consistent underlying algorithm applied across all core operations—comparison, formatting, and diffing—ensuring predictable behavior. It offers granular control over comparisons, notably treating `-0` as distinct from `0`, `NaN` as equal to `NaN`, and comparing functions, promises, and symbols strictly by identity. Formatting is optimized for legibility, including special handling for multi-line strings and control characters. Concordance also supports serialization for later comparison, with specific behavioral changes noted when a deserialized value is used as the 'actual' value in comparisons.
Common errors
-
ReferenceError: require is not defined in ES module scope
cause Attempting to use CommonJS `require()` syntax (e.g., `const { compare } = require('concordance');`) in an ES module environment, such as a `.mjs` file or a project with `"type": "module"` in `package.json`.fixSwitch to ES module import syntax: `import { compare } from 'concordance';` -
TypeError: concordance.compare is not a function
cause This error typically occurs when trying to access `compare` as a property of a non-existent default import, or when `require('concordance')` does not return an object with `compare` as a direct property in a CommonJS context.fixUse named imports for ES Modules: `import { compare } from 'concordance';`. If in a CommonJS context, ensure correct destructuring: `const { compare } = require('concordance');`. -
SyntaxError: Cannot use import statement outside a module
cause Using `import` syntax (e.g., `import { format } from 'concordance';`) in a CommonJS file, which is the default for `.js` files unless `"type": "module"` is set in `package.json` or the file has a `.mjs` extension.fixIf your project is CommonJS, use `const { format } = require('concordance');`. If you intend to use ES Modules, ensure your `package.json` includes `"type": "module"` or rename your file to `.mjs`.
Warnings
- breaking Concordance v3.0.0 introduced a breaking change to how circular references are compared. Previously, all circular references were considered unequal. As of v3.0.0, if the *same* cycle is present in both the actual and expected values, they are considered equal; otherwise, they are unequal.
- breaking Concordance v4.0.0 dropped support for Node.js 4. Users running Node.js environments older than version 6 must upgrade their Node.js runtime to use Concordance v4.x or later.
- breaking Concordance v5.0.0 and subsequent versions explicitly require Node.js 10 or higher. Running on older Node.js versions (e.g., Node.js 8) will result in errors.
- gotcha When comparing deserialized values, specific behaviors change compared to live JavaScript values. For example, `Argument` values can only be compared to other `Argument` values, `Function` values are compared by name, `Promise` values by their constructor and enumerable properties (not identity), and `Symbol` values by their string serialization.
- gotcha Concordance's comparison logic treats `-0` as distinct from `0`, and `NaN` as equal to `NaN`. This behavior deviates from standard JavaScript strict equality (`===`) for both `-0` and `NaN`.
Install
-
npm install concordance -
yarn add concordance -
pnpm add concordance
Imports
- compare
const compare = require('concordance').compareimport { compare } from 'concordance' - format
import concordance from 'concordance'; const formatted = concordance.format(value)
import { format } from 'concordance' - serialize
import { serialize, deserialize } from 'concordance'
Quickstart
import { diff, format } from 'concordance';
interface User {
id: number;
name: string;
email?: string;
address: {
street: string;
city: string;
zip: string;
};
roles: string[];
createdAt: Date;
lastLogin?: Date;
preferences?: Map<string, any>;
}
const user1: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
address: {
street: '123 Main St',
city: 'Anytown',
zip: '12345'
},
roles: ['admin', 'editor'],
createdAt: new Date('2023-01-01T10:00:00Z'),
preferences: new Map([['theme', 'dark'], ['notifications', true]])
};
const user2: User = {
id: 1,
name: 'Alicia', // Changed name
// email missing
address: {
street: '123 Main Street', // Street name changed slightly
city: 'Anytown',
zip: '12345'
},
roles: ['editor'], // Role removed
createdAt: new Date('2023-01-01T10:00:00Z'),
lastLogin: new Date('2024-04-18T15:30:00Z') // New field
};
console.log("--- Diffing two user objects ---");
const userDiff = diff(user1, user2);
console.log(format(userDiff));
const objA = { a: 1, b: { c: 2 } };
const objB = { a: 1, b: { c: 3, d: 4 } };
console.log("\n--- Diffing simple objects ---");
console.log(format(diff(objA, objB)));
// Example of how -0 and 0 are handled distinctly
console.log("\n--- Comparing -0 and 0 ---");
const negativeZeroDiff = diff(-0, 0);
console.log(format(negativeZeroDiff)); // Will show a difference