JSON Object Diffing and Patching
diff-json is a JavaScript library (version 2.0.0, last updated in 2017) designed to generate structural differences between JavaScript objects, and subsequently apply or revert those changes. Inspired by `eugeneware/changeset`, it offers `diff`, `applyChanges`, and `revertChanges` functionalities. A key differentiator is its ability to handle array diffing by specifying a primary key for embedded objects, ensuring accurate comparison beyond simple positional changes. However, due to its age and lack of maintenance since 2017, it does not support modern JavaScript features like ES Modules or TypeScript and has an uncertain future regarding bug fixes or security updates. Alternative modern libraries like `json-diff-ts` offer similar functionality with current ecosystem support.
Common errors
-
ReferenceError: require is not defined in ES module scope
cause Attempting to use `require()` in a JavaScript file that is treated as an ES module (e.g., due to `"type": "module"` in `package.json` or a `.mjs` extension).fixChange the file to CommonJS (`.js` without `"type": "module"` or explicitly `.cjs` extension) or use a bundler to convert the `diff-json` CommonJS module to an ES module format during compilation. Alternatively, migrate to an ES module-compatible diffing library. -
TypeError: changesets.diff is not a function
cause This error typically occurs if the library was imported incorrectly, or if you attempted to destructure `diff` directly from `require('diff-json')` in an environment where it's not supported, expecting named exports that don't exist.fixEnsure you are importing the entire `changesets` object using `const changesets = require('diff-json');` and then calling its methods as `changesets.diff(...)`, `changesets.applyChanges(...)`, etc. -
Unexpected array diff results (e.g., entire objects marked as 'add'/'remove' instead of 'update' for reordered items).
cause When comparing arrays of objects, `diff-json` defaults to index-based comparison if no `embededKey` is provided. If objects within the array are reordered or partially updated without a key, the diff might incorrectly show elements as removed and added rather than updated.fixProvide a unique `embededKey` for array elements in the third argument of `changesets.diff(oldObj, newObj, { arrayKey: 'uniquePropertyName' })` to enable intelligent, key-based array item matching.
Warnings
- breaking The `diff-json` package has been abandoned since its last update in September 2017. This means it lacks support for modern JavaScript features like ES Modules, may contain unpatched bugs or security vulnerabilities, and will not receive new features or maintenance. Users should consider migrating to actively maintained alternatives.
- gotcha This library is CommonJS-only and does not provide native ES module support. Using `import` statements directly will result in errors in native ESM environments (e.g., Node.js with `"type": "module"` in `package.json`) without transpilation.
- gotcha The library does not provide TypeScript type definitions. Developers using TypeScript will have to either write their own declarations or use it without type safety.
- gotcha When diffing arrays containing objects, `diff-json` requires an `embededKey` option to correctly identify and compare elements. If this key is not provided, arrays are diffed by index, which can lead to incorrect or non-intuitive diffs if elements are reordered or new elements are inserted.
Install
-
npm install diff-json -
yarn add diff-json -
pnpm add diff-json
Imports
- changesets
import changesets from 'diff-json';
const changesets = require('diff-json'); - diff
import { diff } from 'diff-json';const { diff } = require('diff-json'); // Although technically possible to destructure, the common pattern is to import the entire object. - applyChanges
import { applyChanges } from 'diff-json';const changesets = require('diff-json'); changesets.applyChanges(oldObj, diffs);
Quickstart
const changesets = require('diff-json');
const oldObj = {
name: 'joe',
age: 55,
coins: [2, 5],
children: [
{name: 'kid1', age: 1},
{name: 'kid2', age: 2}
]
};
const newObj = {
name: 'smith',
coins: [2, 5, 1],
children: [
{name: 'kid3', age: 3},
{name: 'kid1', age: 0},
{name: 'kid2', age: 2}
]
};
// Generate a diff, specifying 'name' as the primary key for children array elements
const diffs = changesets.diff(oldObj, newObj, {children: 'name'});
console.log('Generated Diffs:', JSON.stringify(diffs, null, 2));
/*
Example Output (truncated):
[
{ "type": "update", "key": "name", "value": "smith", "oldValue": "joe" },
{ "type": "update", "key": "coins", "embededKey": "$index", "changes": [ { "type": "add", "key": "2", "value": 1 } ] },
{ "type": "update", "key": "children", "embededKey": "name", "changes": [ { "type": "update", "key": "kid1", "changes": [ { "type": "update", "key": "age", "value": 0, "oldValue": 1 } ] }, { "type": "add", "key": "kid3", "value": { "name": "kid3", "age": 3 } } ] },
{ "type": "remove", "key": "age", "value": 55 }
]
*/
// Apply changes to a base object
const targetObj = JSON.parse(JSON.stringify(oldObj)); // Deep clone to avoid mutating oldObj
changesets.applyChanges(targetObj, diffs);
console.log('\nObject after applying changes:', JSON.stringify(targetObj, null, 2));
// Revert changes on a modified object to get back to the original state
const revertableObj = JSON.parse(JSON.stringify(newObj)); // Start with newObj to revert to oldObj state
const revertedDiffs = changesets.revertChanges(diffs); // Revert the diffs themselves
changesets.applyChanges(revertableObj, revertedDiffs);
console.log('\nObject after reverting changes:', JSON.stringify(revertableObj, null, 2));