1 | var flat = require('flat');
|
2 | var traverse = require('traverse');
|
3 |
|
4 | var isArray = Array.isArray;
|
5 | var isObject = function(obj) {
|
6 | return obj !== null && (typeof obj === 'object') && obj.constructor === Object;
|
7 | };
|
8 |
|
9 | var flatten = function(o, all, prefixes) {
|
10 | var result = {};
|
11 | var add = function(path, value) {
|
12 | var key = path.join('.');
|
13 | var l = 0;
|
14 |
|
15 | for(var i = 0; i < path.length - 1; i++) {
|
16 | l += path[i].length;
|
17 | prefixes[key.slice(0, l + i)] = true;
|
18 | }
|
19 |
|
20 | all[key] = true;
|
21 | result[key] = value;
|
22 | };
|
23 |
|
24 | traverse(o).forEach(function(obj) {
|
25 | if(!isArray(obj) && !isObject(obj)) {
|
26 | if(obj === null || obj === undefined) {
|
27 | return;
|
28 | }
|
29 |
|
30 | var v = obj.constructor.name + '#' + obj;
|
31 | add(this.path, v);
|
32 |
|
33 | this.block();
|
34 | } else if(this.isLeaf && !this.isRoot) {
|
35 |
|
36 | add(this.path, isArray(obj) ? 'Array#[]' : 'Object#{}');
|
37 | }
|
38 | });
|
39 |
|
40 | return result;
|
41 | };
|
42 |
|
43 | var diff = function(a, b, options) {
|
44 | options = options || {};
|
45 |
|
46 | var all = {};
|
47 | var prefixes = {};
|
48 |
|
49 | a = flatten(a, all, prefixes);
|
50 | b = flatten(b, all, prefixes);
|
51 |
|
52 | var result = options.accumulate || {};
|
53 |
|
54 | Object.keys(all).forEach(function(k) {
|
55 | if(prefixes[k]) {
|
56 | return;
|
57 | }
|
58 | if (a[k] === b[k]) {
|
59 | return;
|
60 | }
|
61 |
|
62 | var resultK = options.group ? k.replace(/\.\d+(\.|$)/, '.[*]$1') : k;
|
63 | var r = result[resultK];
|
64 |
|
65 | result[resultK] = r = r || { added: 0, removed: 0, updated: 0 };
|
66 |
|
67 | if(!(k in b)) {
|
68 | return r.removed++;
|
69 | }
|
70 | if(!(k in a)) {
|
71 | return r.added++;
|
72 | }
|
73 |
|
74 | r.updated++;
|
75 | });
|
76 |
|
77 | return result;
|
78 | };
|
79 |
|
80 | var deep = function(a, b) {
|
81 | var change = diff(a, b);
|
82 |
|
83 | Object.keys(change).forEach(function(key) {
|
84 | var c = change[key];
|
85 | change[key] = (c.added && 'added') || (c.removed && 'removed') || (c.updated && 'updated');
|
86 | });
|
87 |
|
88 | change = flat.unflatten(change);
|
89 | change = traverse(change).map(function(obj) {
|
90 | if(!Array.isArray(obj)) {
|
91 | return;
|
92 | }
|
93 |
|
94 | this.update(obj.filter(function(value) {
|
95 | return value !== undefined;
|
96 | }));
|
97 | });
|
98 |
|
99 | return change;
|
100 | };
|
101 |
|
102 | diff.deep = deep;
|
103 |
|
104 | module.exports = diff;
|