This repository has been archived by the owner on Feb 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
126 lines (115 loc) · 4.14 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
var NoValidInputJSONError = require('./NoValidInputJSONError');
/**
* Compares two JSON-objects and returns an array of JSON-patch-operations that represents the difference between the two objects.
* @param {*} objA
* @param {*} objB
*/
function jsonDiff(objA, objB) {
// Check for null arguments
if (objA == null || objB == null) {
throw new NoValidInputJSONError('Cannot compare arguments that are NULL or UNDEFINED.');
}
// Check for string arguments
if (typeof objA === 'string' || typeof objB === 'string') {
throw new NoValidInputJSONError('Cannot compare arguments of type \'string\'.');
}
// Stringify and parse arguments to eliminate non-JSON properties.
// Catch circular references and objects that evaluate to null or undefined when stringified
try {
objA = JSON.parse(JSON.stringify(objA));
objB = JSON.parse(JSON.stringify(objB));
} catch (e) {
if (e.name === 'TypeError') {
throw new NoValidInputJSONError('Cannot compare objects with circular references.');
} else if (e.name === 'SyntaxError') {
throw new NoValidInputJSONError();
}
throw e;
}
// Check for non-object and non-array arguments
if (!((typeof objA === 'object' || objA.constructor === Array) && (typeof objB === 'object' || objB.constructor === Array))) {
throw new NoValidInputJSONError('Cannot compare arguments that are not of type \'object\' or \'Array\'.');
}
// Check for distinct argument types
if (typeof objA !== typeof objB || objA.constructor !== objB.constructor) {
throw new NoValidInputJSONError('Cannot compare argument of type \'' + typeof objA + '\' with argument of type \'' + typeof objB + '\'.');
}
var patches = [];
_compare.bind(patches)(objA, objB, '');
return patches;
}
function _compare(objA, objB, path) {
if (isPrimitive(objA)) {
if (isPrimitive(objB) && typeof objA === typeof objB) {
if (objA !== objB) {
this.push({
op: 'replace',
path: path,
value: objB
});
}
} else {
this.push({
op: 'replace',
path: path,
value: objB
});
}
} else if (objA.constructor === Array || objB.constuctor === Array) {
var maxLength = Math.max(objA.length, objB.length);
for (var i = 0; i < maxLength; i++) {
if (objA[i] != null && objB[i] == null) {
this.push({
op: 'remove',
path: path + '/' + i
});
} else if (objA[i] == null && objB[i] != null) {
this.push({
op: 'add',
path: path + '/' + i,
value: objB[i]
});
} else {
_compare.bind(this)(objA[i], objB[i], path + '/' + i);
}
}
} else {
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
for (var i = 0; i < keysA.length; i++) {
if (keysB.indexOf(keysA[i]) === -1 || (objA[keysA[i]] != null && objB[keysA[i]] == null)) {
this.push({
op: 'remove',
path: path + '/' + keysA[i]
});
} else {
_compare.bind(this)(objA[keysA[i]], objB[keysA[i]], path + '/' + keysA[i]);
}
}
for (var i = 0; i < keysB.length; i++) {
if (keysA.indexOf(keysB[i]) === -1 || (objB[keysB[i]] != null && objA[keysB[i]] == null)) {
this.push({
op: 'add',
path: path + '/' + keysB[i],
value: objB[keysB[i]]
});
}
}
}
}
function isPrimitive(value) {
if (typeof value === 'boolean') {
return true;
}
if (typeof value === 'number') {
return true;
}
if (typeof value === 'string') {
return true;
}
if (value == null) {
return true;
}
return false;
}
module.exports = exports = jsonDiff;