Diff & Patch for JavaScript objects and arrays (ie. any JSON serializable structure)
JsonDiffPatch is a small library that allows to diff object graphs, create a patch (in pure JSON), and apply it to update an original version.
Now available on npm:
npm install jsondiffpatch
or
bower install jsondiffpatch
- Could be used for logging, audit, remote (client-server) synchronization of changes, etc.
- Minified version is < 6KB
- Works in browsers and server (Node.j or any CommonJS env), open test page to check other browsers.
- Automatically detect environment support and load as CommonJS module (eg: node.js), anonymous AMD module (eg: using RequireJS on the browser, no globals), or as browser global.
- For long text diffs uses google-diff_match_patch library if loaded (other text diff libs can be plugged in)
- Arrays diffs are smart!
- Using LCS (the same type of algorithm used by popular text diff tools on lines of text) insertions and deletions are detected efficiently.
- Also detects items moved on the same array (a refinement to LCS). Patching will only move the item in the array, and inner changes in the moved object are diffed/patched too.
- Works with objects in the array if you provide a hash function, eg:
jsondiffpatch.config.objectHash = function(obj) { return obj.id || JSON.stringify(obj); };
. NOTE: If no objectHash function is configured Array items are compared using just===
, meaning{ a: 1 }
is different from{ a: 1 }
unless they reference the same instance, this is by design, because object equality is a problem without trivial solution. Some use-cases are fine with just===
(the default), others need comparison by an id field, others full JSON stringify, be sure to provide yours if default is not enough, as in DEMO page)
- Reverse a diff and unpatch (eg. revert object to its original state based on diff)
- Optional lib included for visualizing diffs as html
-
Objects on the graph means that it's a node in the diff tree and will continue recursively
_t
: (special member) indicates the type of node,a
meansarray
, otherwise it's anobject
.- in arrays,
N
indicates index on the new array,_N
means index at the original array.
-
Arrays in the delta means that the node has changed
[newValue]
-> added[oldValue, newValue]
-> modified[oldValue, 0, 0]
-> deleted[textDiff, 0, 2]
-> text diff["", N, 3]
-> element was moved to N
###Example
Object diffing:
// sample data
var country = {
name: "Argentina",
capital: "Buenos Aires",
independence: new Date(1816, 6, 9),
unasur: true
};
// clone country, using dateReviver for Date objects
var country2 = JSON.parse(JSON.stringify(country),jsondiffpatch.dateReviver);
// make some changes
country2.name = "Rep�blica Argentina";
country2.population = "41324992";
delete country2.capital;
var delta = jsondiffpatch.diff(country,country2);
/*
delta = {
"name":["Argentina","Rep�blica Argentina"], // old value, new value
"population":["41324992"], // new value
"capital":["Buenos Aires",0,0] // deleted
}
*/
// patch original
jsondiffpatch.patch(country, delta);
// reverse diff
var reverseDelta = jsondiffpatch.reverse(delta);
// also country2 can be return to original value with: jsondiffpatch.unpatch(country2, delta);
var delta2 = jsondiffpatch.diff(country,country2);
// delta2 is undefined, no difference
Array diffing:
// sample data
var country = {
name: "Argentina",
cities: [
{
name: 'Buenos Aires',
population: 13028000,
},
{
name: 'C�rdoba',
population: 1430023,
},
{
name: 'Rosario',
population: 1136286,
},
{
name: 'Mendoza',
population: 901126,
},
{
name: 'San Miguel de Tucum�n',
population: 800000,
}
]
};
// clone country
var country2 = JSON.parse(JSON.stringify(country));
// delete C�rdoba
country.cities.splice(1, 1);
// add La Plata
country.cities.splice(4, 0, {
name: 'La Plata'
});
// modify Rosario, and move it
var rosario = country.cities.splice(1, 1)[0];
rosario.population += 1234;
country.cities.push(rosario);
// match objects by name
jsondiffpatch.config.objectHash = function(obj) {
return obj.name;
}
var delta = jsondiffpatch.diff(country,country2);
/*
delta = {
"cities": {
"1": [
// inserted at index 1
{
"name": "C�rdoba",
"population": 1430023
}]
,
"2": {
// population modified at index 2 (Rosario)
"population": [
1137520,
1136286
]
},
"_t": "a",
"_3": [
// removed from index 3
{
"name": "La Plata"
},0,0],
"_4": [
// move from index 4 to index 2
'',2,3]
}
}
*/
For more complex cases (nested objects or arrays, long text diffs) check unit tests in /test/test.js
To use as AMD module (eg: using RequireJS on the browser):
require('jsondiffpatch', function(jsondiffpatch){
// code using jsondiffpatch
});
// a module that depends on jsondiffpatch
define('mytexteditor.visualcomparer', ['jsondiffpatch'], function(jsondiffpatch){
// module implementation using jsondiffpatch
});
- Tested on Chrome, FireFox, IE7+, to check other browsers open test page to run unit tests.
- Node.js
QUnit is used for unit testing. Just open the test page on your preferred browser.
To run tests on Node.js on jsondiffpatch root folder:
npm i
npm test
A minified version is provided as jsondiffpatch.min.js To regenerate that file run (npm i is required as uglifyjs is used):
npm i
npm run-script minify
Install using npm:
npm install jsondiffpatch
or, Download the latest release from the web site (http://github.com/benjamine/JsonDiffPatch) and copy
jsondiffpatch.min.js
to a suitable location. To support text diffs include Google's diff_match_patch.
Then include it in your HTML like so:
<script type="text/javascript" src="/path/to/jsondiffpatch.min.js"></script>
<script type="text/javascript" src="/path/to/diff_match_patch_uncompressed.js"></script>
Note: you can use JsonDiffPatch on browserless JavaScript environments too (as Node.js, or Mozilla Rhino).
On Node.js you have to connect your text diff/patch library explicitly. eg:
var jsondiffpatch = require('./jsondiffpatch.js');
// load google diff_match_patch library for text diff/patch
jsondiffpatch.config.diff_match_patch = require('./diff_match_patch_uncompressed.js');
// use text diff for strings longer than 5 chars
jsondiffpatch.config.textDiffMinLength = 5;
var d = jsondiffpatch.diff({ age: 5, name: 'Arturo' }, {age: 7, name: 'Armando' });
// d = {
// age: [ 5, 7 ],
// name: [ '@@ -1,6 +1,7 @@\n Ar\n-tur\n+mand\n o\n', 0, 2 ] }
console.log(d.name[0])
// prints:
// @@ -1,6 +1,7 @@
// Ar
// -tur
// +mand
// o
To visualize diffs you can include JsonDiffPatch.Html script + css on your page:
<script type="text/javascript" src="/path/to/jsondiffpatch.html.js"></script>
<link rel="stylesheet" href="../src/jsondiffpatch.html.css" type="text/css" />
Now you can use the jsondiffpatch.html.diffToHtml() function to visualize diffs as html:
var json1 = JSON.parse($('#json1').val());
var json2 = JSON.parse($('#json2').val());
var d = jsondiffpatch.diff(json1, json2);
$('#myvisualdiffcontainer').empty().append(jsondiffpatch.html.diffToHtml(json1, json2, d));
To see this in action check the DEMO page.
Also you can generate diffs with jsondiffpatch on your console:
jsondiffpatch .\test\testdata.json .\test\testdata2.json