These libraries solve the problem of identifying changes between two states, but they target different data types and use cases. deep-diff specializes in comparing JavaScript objects to track state mutations. diff and diff-match-patch focus on text comparison, with the latter offering advanced patching and fuzzy matching capabilities. diff2html does not calculate differences itself but renders diff output into human-readable HTML for user interfaces.
When building tools that track changes β whether for state management, code review, or text editing β picking the right library depends heavily on your data type and end goal. deep-diff, diff, diff-match-patch, and diff2html all handle "differences," but they operate at different layers of the stack. Let's break down their core mechanics, output formats, and ideal use cases.
The first decision point is simple: are you comparing data structures or strings?
deep-diff works exclusively with JavaScript objects.
// deep-diff: Comparing objects
import Diff from 'deep-diff';
const lhs = { name: 'Alice', role: 'Dev' };
const rhs = { name: 'Alice', role: 'Engineer' };
const changes = Diff.diff(lhs, rhs);
// Output: [ { kind: 'E', path: ['role'], lhs: 'Dev', rhs: 'Engineer' } ]
diff and diff-match-patch work with strings.
diff is strict and precise.diff-match-patch allows for fuzzy matching and patch application.// diff: Comparing strings
import { diffChars } from 'diff';
const change = diffChars('cat', 'cut');
// Output: [ { count: 1, value: 'c' }, { count: 1, value: 'a', removed: true }, { count: 1, value: 'u', added: true }, ... ]
// diff-match-patch: Comparing strings with patches
import { diff_match_patch } from 'diff-match-patch';
const dmp = new diff_match_patch();
const diffs = dmp.diff_main('cat', 'cut');
// Output: [ [0, 'c'], [-1, 'a'], [1, 'u'], [0, 't'] ]
diff2html is purely for presentation.
diff or Git) and converts it to HTML.// diff2html: Rendering diff output
import { Diff2HtmlUI } from 'diff2html';
const diffString = `diff --git a/file.js b/file.js\nindex 123..456 100644\n--- a/file.js\n+++ b/file.js\n@@ -1 +1 @@\n-const a = 1;\n+const a = 2;`;
const targetElement = document.getElementById('myDiff');
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString);
diff2htmlUi.draw();
How the library calculates differences impacts performance and accuracy.
diff uses the Myers diff algorithm.
diff-match-patch uses a mix of line-based and character-based logic with cleanup phases.
deep-diff uses recursive traversal.
The shape of the output determines how easy it is to consume.
| Package | Input Type | Output Shape | Primary Use |
|---|---|---|---|
deep-diff | Objects | Array of Edit Objects | State Tracking |
diff | Strings | Array of Change Objects | Text Comparison |
diff-match-patch | Strings | Array of Tuples + Patches | Text Editing |
diff2html | Diff Strings | HTML String / DOM | UI Rendering |
deep-diff returns a structured array you can iterate to apply changes elsewhere.
// deep-diff: Iterating changes
changes.forEach((change) => {
if (change.kind === 'E') {
console.log(`Changed ${change.path.join('.')} from ${change.lhs} to ${change.rhs}`);
}
});
diff returns objects with added, removed, and value flags.
// diff: Checking flags
change.forEach((part) => {
const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
// Render part.value with color
});
diff-match-patch returns tuples: [operation, text]. Operation 0 = equal, -1 = deleted, 1 = inserted.
// diff-match-patch: Parsing tuples
diffs.forEach((part) => {
const op = part[0]; // 0, -1, or 1
const text = part[1];
// Process based on op
});
diff2html consumes unified diff strings (the kind Git produces).
// diff2html: Configuration
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, {
drawFileList: true,
matching: 'lines',
outputFormat: 'side-by-side'
});
You need to log exactly what changed in your store for debugging.
deep-diff// deep-diff in middleware
const changes = Diff.diff(prevState, nextState);
if (changes) {
logger.warn('State mutated', changes);
}
You are building a GitHub-like interface to show code changes.
diff + diff2htmldiff calculates the changes accurately. diff2html renders them nicely.// diff + diff2html pipeline
const changes = Diff.createTwoFilesPatch('old.js', 'new.js', oldCode, newCode);
const html = Diff2Html.html(changes);
document.body.innerHTML = html;
Users are typing simultaneously, and you need to sync changes over a network.
diff-match-patch// diff-match-patch for syncing
const patches = dmp.patch_make(text1, text2);
// Send patches over network
const newText = dmp.patch_apply(patches, text1)[0];
You want to alert users if their local config file differs from the default.
deep-diff// deep-diff for config
const diffs = Diff.diff(defaultConfig, userConfig);
const hasDrift = diffs && diffs.length > 0;
deep-diff is stable and widely used for object comparison.
diff (jsdiff) is the standard for JavaScript text diffing and is actively maintained.
diff-match-patch is older and receives fewer updates, but the algorithm is proven and stable.
diff2html is actively maintained and follows modern web standards for rendering.
None of these packages are deprecated, but diff-match-patch sees less frequent feature updates because the underlying problem is solved.
| Feature | deep-diff | diff | diff-match-patch | diff2html |
|---|---|---|---|---|
| Input | Objects | Strings | Strings | Diff Strings |
| Output | Edit Array | Change Array | Tuples + Patches | HTML |
| Logic | Recursive Tree | Myers Algorithm | Fuzzy + Patch | Rendering |
| UI Ready | No | No | No | Yes |
| Best For | State/Config | Code/Text | Editors/Sync | Display |
These tools solve different parts of the "change detection" puzzle.
deep-diff is your choice for data. Use it when your source of truth is a JavaScript object, like application state or settings.
diff is your choice for text accuracy. Use it when you need a precise, standard comparison of strings, like source code.
diff-match-patch is your choice for text resilience. Use it when you need to apply changes to text that might have moved, like in a collaborative editor.
diff2html is your choice for display. Use it when you have diff data and need to show it to a human in a browser.
Final Thought: In many complex apps, you will use more than one. A code review tool might use diff to calculate changes and diff2html to show them. A state debugger might use deep-diff to find changes and a custom component to render them. Choose based on your input data and your output needs.
Choose deep-diff when you need to track changes in JavaScript objects, such as application state or configuration files. It is ideal for scenarios where you need to know exactly which properties changed, were added, or removed, rather than just comparing text representations.
Choose diff for standard text comparison tasks where accuracy is critical, such as code review tools or string validation. It provides a reliable implementation of the Myers diff algorithm and is well-suited for comparing source code or structured text files.
Choose diff-match-patch if you are building a text editor or collaborative tool that requires fuzzy matching or the ability to apply patches to text. It excels in scenarios where text might be edited concurrently or where you need to reconstruct text from a series of changes.
Choose diff2html when you need to display differences to end users in a web interface. It is the go-to solution for rendering diff data (generated by diff or Git) into styled HTML, supporting side-by-side or line-by-line views without building a renderer from scratch.
deep-diff is a javascript/node.js module providing utility functions for determining the structural differences between objects and includes some utilities for applying differences across objects.
npm install deep-diff
Possible v1.0.0 incompatabilities:
deep-diff does a pre-order traversal of the object graph, however, when it encounters an array, the array is processed from the end towards the front, with each element recursively processed in-order during further descent.npm install deep-diff
var diff = require('deep-diff')
// or:
// const diff = require('deep-diff');
// const { diff } = require('deep-diff');
// or:
// const DeepDiff = require('deep-diff');
// const { DeepDiff } = require('deep-diff');
// es6+:
// import diff from 'deep-diff';
// import { diff } from 'deep-diff';
// es6+:
// import DeepDiff from 'deep-diff';
// import { DeepDiff } from 'deep-diff';
<script src="https://cdn.jsdelivr.net/npm/deep-diff@1/dist/deep-diff.min.js"></script>
In a browser,
deep-diffdefines a global variableDeepDiff. If there is a conflict in the global namespace you can restore the conflicting definition and assigndeep-diffto another variable like this:var deep = DeepDiff.noConflict();.
In order to describe differences, change revolves around an origin object. For consistency, the origin object is always the operand on the left-hand-side of operations. The comparand, which may contain changes, is always on the right-hand-side of operations.
var diff = require('deep-diff').diff;
var lhs = {
name: 'my object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'elements']
}
};
var rhs = {
name: 'updated object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'more', 'elements', { than: 'before' }]
}
};
var differences = diff(lhs, rhs);
v 0.2.0 and above The code snippet above would result in the following structure describing the differences:
[ { kind: 'E',
path: [ 'name' ],
lhs: 'my object',
rhs: 'updated object' },
{ kind: 'E',
path: [ 'details', 'with', 2 ],
lhs: 'elements',
rhs: 'more' },
{ kind: 'A',
path: [ 'details', 'with' ],
index: 3,
item: { kind: 'N', rhs: 'elements' } },
{ kind: 'A',
path: [ 'details', 'with' ],
index: 4,
item: { kind: 'N', rhs: { than: 'before' } } } ]
Differences are reported as one or more change records. Change records have the following structure:
kind - indicates the kind of change; will be one of the following:
N - indicates a newly added property/elementD - indicates a property/element was deletedE - indicates a property/element was editedA - indicates a change occurred within an arraypath - the property path (from the left-hand-side root)lhs - the value on the left-hand-side of the comparison (undefined if kind === 'N')rhs - the value on the right-hand-side of the comparison (undefined if kind === 'D')index - when kind === 'A', indicates the array index where the change occurreditem - when kind === 'A', contains a nested change record indicating the change that occurred at the array indexChange records are generated for all structural differences between origin and comparand. The methods only consider an object's own properties and array elements; those inherited from an object's prototype chain are not considered.
Changes to arrays are recorded simplistically. We care most about the shape of the structure; therefore we don't take the time to determine if an object moved from one slot in the array to another. Instead, we only record the structural
differences. If the structural differences are applied from the comparand to the origin then the two objects will compare as "deep equal" using most isEqual implementations such as found in lodash or underscore.
When two objects differ, you can observe the differences as they are calculated and selectively apply those changes to the origin object (left-hand-side).
var observableDiff = require('deep-diff').observableDiff;
var applyChange = require('deep-diff').applyChange;
var lhs = {
name: 'my object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'elements']
}
};
var rhs = {
name: 'updated object',
description: 'it\'s an object!',
details: {
it: 'has',
an: 'array',
with: ['a', 'few', 'more', 'elements', { than: 'before' }]
};
observableDiff(lhs, rhs, function (d) {
// Apply all changes except to the name property...
if (d.path[d.path.length - 1] !== 'name') {
applyChange(lhs, rhs, d);
}
});
A standard import of var diff = require('deep-diff') is assumed in all of the code examples. The import results in an object having the following public properties:
diff(lhs, rhs, prefilter, acc) β calculates the differences between two objects, optionally prefiltering elements for comparison, and optionally using the specified accumulator.observableDiff(lhs, rhs, observer, prefilter) β calculates the differences between two objects and reports each to an observer function, optionally, prefiltering elements for comparison.applyDiff(target, source, filter) β applies any structural differences from a source object to a target object, optionally filtering each difference.applyChange(target, source, change) β applies a single change record to a target object. NOTE: source is unused and may be removed.revertChange(target, source, change) reverts a single change record to a target object. NOTE: source is unused and may be removed.diffThe diff function calculates the difference between two objects.
lhs - the left-hand operand; the origin object.rhs - the right-hand operand; the object being compared structurally with the origin object.prefilter - an optional function that determines whether difference analysis should continue down the object graph.acc - an optional accumulator/array (requirement is that it have a push function). Each difference is pushed to the specified accumulator.Returns either an array of changes or, if there are no changes, undefined. This was originally chosen so the result would be pass a truthy test:
var changes = diff(obja, objb);
if (changes) {
// do something with the changes.
}
The prefilter's signature should be function(path, key) and it should return a truthy value for any path-key combination that should be filtered. If filtered, the difference analysis does no further analysis of on the identified object-property path.
const diff = require('deep-diff');
const assert = require('assert');
const data = {
issue: 126,
submittedBy: 'abuzarhamza',
title: 'readme.md need some additional example prefilter',
posts: [
{
date: '2018-04-16',
text: `additional example for prefilter for deep-diff would be great.
https://stackoverflow.com/questions/38364639/pre-filter-condition-deep-diff-node-js`
}
]
};
const clone = JSON.parse(JSON.stringify(data));
clone.title = 'README.MD needs additional example illustrating how to prefilter';
clone.disposition = 'completed';
const two = diff(data, clone);
const none = diff(data, clone,
(path, key) => path.length === 0 && ~['title', 'disposition'].indexOf(key)
);
assert.equal(two.length, 2, 'should reflect two differences');
assert.ok(typeof none === 'undefined', 'should reflect no differences');
When contributing, keep in mind that it is an objective of deep-diff to have no package dependencies. This may change in the future, but for now, no-dependencies.
Please run the unit tests before submitting your PR: npm test. Hopefully your PR includes additional unit tests to illustrate your change/modification!
When you run npm test, linting will be performed and any linting errors will fail the tests... this includes code formatting.
Thanks to all those who have contributed so far!