Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

json-diff-ts

ltwlf722.2kMIT4.8.1TypeScript support: included

Modern TypeScript JSON diff library - Zero dependencies, high performance, ESM + CommonJS support. Calculate and apply differences between JSON objects with advanced features like key-based array diffing, JSONPath support, and atomic changesets.

json, diff, difference, typescript, javascript, compare, patch, delta, object-diff, json-diff, json-patch, state-management, data-sync, merge, changeset, array-diff, deep-diff, object-compare, jsonpath, modern, esm, zero-dependencies, performance

readme

json-diff-ts

CI codecov npm version npm downloads npm bundle size Known Vulnerabilities Quality Gate Status TypeScript PRs Welcome License: MIT

Overview

Modern TypeScript JSON diff library - json-diff-ts is a lightweight, high-performance TypeScript library for calculating and applying differences between JSON objects. Perfect for modern web applications, state management, data synchronization, and real-time collaborative editing.

🚀 Why Choose json-diff-ts?

  • 🔥 Zero dependencies - Lightweight bundle size
  • ⚡ High performance - Optimized algorithms for fast JSON diffing and patching
  • 🎯 95%+ test coverage - Thoroughly tested with comprehensive test suite
  • 📦 Modern ES modules - Full TypeScript support with tree-shaking
  • 🔧 Flexible API - Compare, diff, patch, and atomic operations
  • 🌐 Universal - Works in browsers, Node.js, and edge environments
  • ✅ Production ready - Used in enterprise applications worldwide
  • 🎯 TypeScript-first - Full type safety and IntelliSense support
  • 🔧 Modern features - ESM + CommonJS, JSONPath, atomic operations
  • 📦 Production ready - Battle-tested with comprehensive test suite

Key Features

  • Key-based array identification: Compare array elements using keys instead of indices for more intuitive diffing
  • JSONPath support: Target specific parts of JSON documents with precision
  • Atomic changesets: Transform changes into granular, independently applicable operations
  • Dual module support: Works with both ECMAScript Modules and CommonJS
  • Type change handling: Flexible options for handling data type changes
  • Path skipping: Skip nested paths during comparison for performance

This library is particularly valuable for applications where tracking changes in JSON data is crucial, such as state management systems, form handling, or data synchronization.

Installation

npm install json-diff-ts

Quick Start

import { diff, applyChangeset } from 'json-diff-ts';

// Two versions of data
const oldData = { name: 'Luke', level: 1, skills: ['piloting'] };
const newData = { name: 'Luke Skywalker', level: 5, skills: ['piloting', 'force'] };

// Calculate differences
const changes = diff(oldData, newData);
console.log(changes);
// Output: [
//   { type: 'UPDATE', key: 'name', value: 'Luke Skywalker', oldValue: 'Luke' },
//   { type: 'UPDATE', key: 'level', value: 5, oldValue: 1 },
//   { type: 'ADD', key: 'skills', value: 'force', embeddedKey: '1' }
// ]

// Apply changes to get the new object
const result = applyChangeset(oldData, changes);
console.log(result); // { name: 'Luke Skywalker', level: 5, skills: ['piloting', 'force'] }

Import Options

TypeScript / ES Modules:

import { diff } from 'json-diff-ts';

CommonJS:

const { diff } = require('json-diff-ts');

Core Features

diff

Generates a difference set for JSON objects. When comparing arrays, if a specific key is provided, differences are determined by matching elements via this key rather than array indices.

Basic Example with Star Wars Data

import { diff } from 'json-diff-ts';

// State during A New Hope - Desert planet, small rebel cell
const oldData = {
  location: 'Tatooine',
  mission: 'Rescue Princess',
  status: 'In Progress',
  characters: [
    { id: 'LUKE_SKYWALKER', name: 'Luke Skywalker', role: 'Farm Boy', forceTraining: false },
    { id: 'LEIA_ORGANA', name: 'Princess Leia', role: 'Prisoner', forceTraining: false }
  ],
  equipment: ['Lightsaber', 'Blaster']
};

// State after successful rescue - Base established, characters evolved
const newData = {
  location: 'Yavin Base',
  mission: 'Destroy Death Star',
  status: 'Complete',
  characters: [
    { id: 'LUKE_SKYWALKER', name: 'Luke Skywalker', role: 'Pilot', forceTraining: true, rank: 'Commander' },
    { id: 'HAN_SOLO', name: 'Han Solo', role: 'Smuggler', forceTraining: false, ship: 'Millennium Falcon' }
  ],
  equipment: ['Lightsaber', 'Blaster', 'Bowcaster', 'X-wing Fighter']
};

const diffs = diff(oldData, newData, { embeddedObjKeys: { characters: 'id' } });
console.log(diffs);
// First operations:
// [
//   { type: 'UPDATE', key: 'location', value: 'Yavin Base', oldValue: 'Tatooine' },
//   { type: 'UPDATE', key: 'mission', value: 'Destroy Death Star', oldValue: 'Rescue Princess' },
//   { type: 'UPDATE', key: 'status', value: 'Complete', oldValue: 'In Progress' },
//   ...
// ]

Advanced Options

Path-based Key Identification
import { diff } from 'json-diff-ts';

// Using nested paths for sub-arrays
const diffs = diff(oldData, newData, { embeddedObjKeys: { 'characters.equipment': 'id' } });

// Designating root with '.' - useful for complex nested structures
const diffs = diff(oldData, newData, { embeddedObjKeys: { '.characters.allies': 'id' } });
Type Change Handling
import { diff } from 'json-diff-ts';

// Control how type changes are treated
const diffs = diff(oldData, newData, { treatTypeChangeAsReplace: false });

Date objects can now be updated to primitive values without errors when treatTypeChangeAsReplace is set to false.

Skip Nested Paths
import { diff } from 'json-diff-ts';

// Skip specific nested paths from comparison - useful for ignoring metadata
const diffs = diff(oldData, newData, { keysToSkip: ['characters.metadata'] });
Dynamic Key Resolution
import { diff } from 'json-diff-ts';

// Use function to resolve object keys dynamically
const diffs = diff(oldData, newData, {
  embeddedObjKeys: {
    characters: (obj, shouldReturnKeyName) => (shouldReturnKeyName ? 'id' : obj.id)
  }
});
Regular Expression Paths
import { diff } from 'json-diff-ts';

// Use regex for path matching - powerful for dynamic property names
const embeddedObjKeys = new Map();
embeddedObjKeys.set(/^characters/, 'id');  // Match any property starting with 'characters'
const diffs = diff(oldData, newData, { embeddedObjKeys });
String Array Comparison
import { diff } from 'json-diff-ts';

// Compare string arrays by value instead of index - useful for tags, categories
const diffs = diff(oldData, newData, { embeddedObjKeys: { equipment: '$value' } });

atomizeChangeset and unatomizeChangeset

Transform complex changesets into a list of atomic changes (and back), each describable by a JSONPath.

import { atomizeChangeset, unatomizeChangeset } from 'json-diff-ts';

// Create atomic changes
const atomicChanges = atomizeChangeset(diffs);

// Restore the changeset from a selection of atomic changes
const changeset = unatomizeChangeset(atomicChanges.slice(0, 3));

Atomic Changes Structure:

[
  { 
    type: 'UPDATE', 
    key: 'location', 
    value: 'Yavin Base', 
    oldValue: 'Tatooine', 
    path: '$.location', 
    valueType: 'String' 
  },
  { 
    type: 'UPDATE', 
    key: 'mission', 
    value: 'Destroy Death Star', 
    oldValue: 'Rescue Princess', 
    path: '$.mission', 
    valueType: 'String' 
  },
  { 
    type: 'ADD', 
    key: 'rank', 
    value: 'Commander', 
    path: "$.characters[?(@.id=='LUKE_SKYWALKER')].rank", 
    valueType: 'String' 
  },
  { 
    type: 'ADD', 
    key: 'HAN_SOLO', 
    value: { id: 'HAN_SOLO', name: 'Han Solo', role: 'Smuggler', forceTraining: false, ship: 'Millennium Falcon' }, 
    path: "$.characters[?(@.id=='HAN_SOLO')]", 
    valueType: 'Object' 
  }
]

applyChangeset and revertChangeset

Apply or revert changes to JSON objects.

import { applyChangeset, revertChangeset } from 'json-diff-ts';

// Apply changes
const updated = applyChangeset(oldData, diffs);
console.log(updated);
// { location: 'Yavin Base', mission: 'Destroy Death Star', status: 'Complete', ... }

// Revert changes
const reverted = revertChangeset(newData, diffs);
console.log(reverted);
// { location: 'Tatooine', mission: 'Rescue Princess', status: 'In Progress', ... }

API Reference

Core Functions

Function Description Parameters
diff(oldObj, newObj, options?) Generate differences between two objects oldObj: Original object
newObj: Updated object
options: Optional configuration
applyChangeset(obj, changeset) Apply changes to an object obj: Object to modify
changeset: Changes to apply
revertChangeset(obj, changeset) Revert changes from an object obj: Object to modify
changeset: Changes to revert
atomizeChangeset(changeset) Convert changeset to atomic changes changeset: Nested changeset
unatomizeChangeset(atomicChanges) Convert atomic changes back to nested changeset atomicChanges: Array of atomic changes

Comparison Functions

Function Description Parameters
compare(oldObj, newObj) Create enriched comparison object oldObj: Original object
newObj: Updated object
enrich(obj) Create enriched representation of object obj: Object to enrich
createValue(value) Create value node for comparison value: Any value
createContainer(value) Create container node for comparison value: Object or Array

Options Interface

interface Options {
  embeddedObjKeys?: Record<string, string | Function> | Map<string | RegExp, string | Function>;
  keysToSkip?: string[];
  treatTypeChangeAsReplace?: boolean;
}
Option Type Description
embeddedObjKeys `Record<string, string Function>orMap<string
keysToSkip string[] Dotted paths to exclude from comparison, e.g. "meta.info".
treatTypeChangeAsReplace boolean When true (default), a type change results in a REMOVE/ADD pair. Set to false to treat it as an UPDATE.

Change Types

enum Operation {
  REMOVE = 'REMOVE',
  ADD = 'ADD', 
  UPDATE = 'UPDATE'
}

Release Notes

  • v4.8.1: Improved documentation with working examples and detailed options.
  • v4.8.0: Significantly reduced bundle size by completely removing es-toolkit dependency and implementing custom utility functions. This change eliminates external dependencies while maintaining identical functionality and improving performance.

  • v4.7.0: Optimized bundle size and performance by replacing es-toolkit/compat with es-toolkit for difference, intersection, and keyBy functions

  • v4.6.3: Fixed null comparison returning update when values are both null (fixes issue #284)

  • v4.6.2: Fixed updating to null when treatTypeChangeAsReplace is false and bumped Jest dev dependencies

  • v4.6.1: Consistent JSONPath format for array items (fixes issue #269)
  • v4.6.0: Fixed filter path regex to avoid polynomial complexity
  • v4.5.1: Updated package dependencies
  • v4.5.0: Switched internal utilities from lodash to es-toolkit/compat for a smaller bundle size
  • v4.4.0: Fixed Date-to-string diff when treatTypeChangeAsReplace is false
  • v4.3.0: Enhanced functionality:
    • Added support for nested keys to skip using dotted path notation in the keysToSkip option
    • This allows excluding specific nested object paths from comparison (fixes #242)
  • v4.2.0: Improved stability with multiple fixes:
    • Fixed object handling in atomizeChangeset and unatomizeChangeset
    • Fixed array handling in applyChangeset and revertChangeset
    • Fixed handling of null values in applyChangeset
    • Fixed handling of empty REMOVE operations when diffing from undefined
  • v4.1.0: Full support for ES modules while maintaining CommonJS compatibility
  • v4.0.0: Changed naming of flattenChangeset and unflattenChanges to atomizeChangeset and unatomizeChangeset; added option to set treatTypeChangeAsReplace
  • v3.0.1: Fixed issue with unflattenChanges when a key has periods
  • v3.0.0: Added support for both CommonJS and ECMAScript Modules. Replaced lodash-es with lodash to support both module formats
  • v2.2.0: Fixed lodash-es dependency, added exclude keys option, added string array comparison by value
  • v2.1.0: Fixed JSON Path filters by replacing single equal sign (=) with double equal sign (==). Added support for using '.' as root in paths
  • v2.0.0: Upgraded to ECMAScript module format with optimizations and improved documentation. Fixed regex path handling (breaking change: now requires Map instead of Record for regex paths)
  • v1.2.6: Enhanced JSON Path handling for period-inclusive segments
  • v1.2.5: Added key name resolution support for key functions
  • v1.2.4: Documentation updates and dependency upgrades
  • v1.2.3: Updated dependencies and TypeScript

Contributing

Contributions are welcome! Please follow the provided issue templates and code of conduct.

Performance & Bundle Size

  • Zero dependencies: No external runtime dependencies
  • Lightweight: ~21KB minified, ~6KB gzipped
  • Tree-shakable: Use only what you need with ES modules
  • High performance: Optimized for large JSON objects and arrays

Use Cases

  • State Management: Track changes in Redux, Zustand, or custom state stores
  • Form Handling: Detect field changes in React, Vue, or Angular forms
  • Data Synchronization: Sync data between client and server efficiently
  • Version Control: Implement undo/redo functionality
  • API Optimization: Send only changed data to reduce bandwidth
  • Real-time Updates: Track changes in collaborative applications

Comparison with Alternatives

Feature json-diff-ts deep-diff jsondiffpatch
TypeScript ✅ Native ❌ Partial ❌ Definitions only
Bundle Size 🟢 21KB 🟡 45KB 🔴 120KB+
Dependencies 🟢 Zero 🟡 Few 🔴 Many
ESM Support ✅ Native ❌ CJS only ❌ CJS only
Array Key Matching ✅ Advanced ❌ Basic ✅ Advanced
JSONPath Support ✅ Full ❌ None ❌ Limited

FAQ

Q: Can I use this with React/Vue/Angular?
A: Yes! json-diff-ts works with any JavaScript framework or vanilla JS.

Q: Does it work with Node.js?
A: Absolutely! Supports Node.js 18+ with both CommonJS and ES modules.

Q: How does it compare to JSON Patch (RFC 6902)?
A: json-diff-ts provides a more flexible format with advanced array handling, while JSON Patch is a standardized format.

Q: Is it suitable for large objects?
A: Yes, the library is optimized for performance and can handle large, complex JSON structures efficiently.

Contact

Reach out to the maintainer:

Discover more about the company behind this project: hololux

Acknowledgments

This project takes inspiration and code from diff-json by viruschidai@gmail.com.

License

json-diff-ts is open-sourced software licensed under the MIT license.

The original diff-json project is also under the MIT License. For more information, refer to its license details.