UNPKG

2.91 kBJavaScriptView Raw
1/** @license MIT License (c) copyright 2010-2014 original author or authors */
2/** @author Brian Cavalier */
3/** @author John Hann */
4
5var lcs = require('./lib/lcs');
6var patch = require('./lib/jsonPatch');
7var jsonPointer = require('./lib/jsonPointer');
8var encodeSegment = jsonPointer.encodeSegment;
9
10exports.diff = diff;
11exports.patch = patch.apply;
12exports.clone = patch.clone;
13
14/**
15 * Compute a JSON Patch representing the differences between a and b.
16 * @param {object|array|string|number} a
17 * @param {object|array|string|number} b
18 * @param {function} hasher hashing function that will be used to
19 * recognize identical objects
20 * @returns {array} JSON Patch such that patch(diff(a, b), a) === b
21 */
22function diff(a, b, hasher) {
23 var hash = typeof hasher === 'function' ? hasher : defaultHash;
24 var state = { patch: [], hash: hash };
25
26 return appendChanges(a, b, '', state).patch.reverse();
27}
28
29function appendChanges(a, b, path, state) {
30 if(Array.isArray(b)) {
31 return appendListChanges(a, b, path, state);
32 } else if(b && typeof b === 'object') {
33 return appendObjectChanges(a, b, path, state);
34 } else {
35 return appendValueChanges(a, b, path, state);
36 }
37}
38
39function appendObjectChanges(o1, o2, path, state) {
40 state = Object.keys(o2).reduceRight(function(state, key) {
41 var keyPath = path + '/' + encodeSegment(key);
42 if(key in o1) {
43 appendChanges(o1[key], o2[key], keyPath, state);
44 } else {
45 state.patch.push({
46 op: 'add',
47 path: keyPath,
48 value: o2[key]
49 });
50 }
51
52 return state;
53 }, state);
54
55 return Object.keys(o1).reduceRight(function(state, key) {
56 if(!(key in o2)) {
57 state.patch.push({
58 op: 'remove',
59 path: path + '/' + encodeSegment(key),
60 value: void 0
61 });
62 }
63
64 return state;
65 }, state);
66}
67
68function appendListChanges(a1, a2, path, state) {
69 var a1hash = a1.map(state.hash);
70 var a2hash = a2.map(state.hash);
71
72 var lcsMatrix = lcs.compare(a1hash, a2hash);
73
74 return lcsToJsonPatch(a1, a2, path, state, lcsMatrix);
75}
76
77function lcsToJsonPatch(a1, a2, path, state, lcsMatrix) {
78 lcs.reduce(function(state, op, i, j) {
79 var last, p;
80 if (op.type == lcs.REMOVE) {
81 last = state.patch[state.patch.length-1];
82 p = path + '/' + j;
83 if(last !== void 0 && last.op === 'add' && last.path === p) {
84 last.op = 'replace';
85 } else {
86 state.patch.push({
87 op: 'remove',
88 path: p,
89 value: void 0
90 });
91 }
92 } else if (op.type == lcs.ADD) {
93 state.patch.push({
94 op: 'add',
95 path: path + '/' + j,
96 value: a2[i]
97 });
98 } else {
99 appendChanges(a1[j], a2[i], path + '/' + j, state);
100 }
101
102 return state;
103 }, state, lcsMatrix);
104
105 return state;
106}
107
108function appendValueChanges(a, b, path, state) {
109 if(a !== b) {
110 state.patch.push({
111 op: 'replace',
112 path: path,
113 value: b
114 });
115 }
116
117 return state;
118}
119
120function defaultHash(x, i) {
121 return x !== null && typeof x === 'object' && 'id' in x
122 ? x.id : JSON.stringify(x);
123}