json-diff-ts 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/lib/jsonDiff.d.ts +9 -1
- package/lib/jsonDiff.js +79 -34
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ const oldData = {
|
|
|
33
33
|
characters: [
|
|
34
34
|
{ id: 'LUK', name: 'Luke Skywalker', force: true },
|
|
35
35
|
{ id: 'LEI', name: 'Leia Organa', force: true }
|
|
36
|
-
]
|
|
36
|
+
],
|
|
37
|
+
weapons: ['Lightsaber', 'Blaster']
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
const newData = {
|
|
@@ -42,7 +43,8 @@ const newData = {
|
|
|
42
43
|
characters: [
|
|
43
44
|
{ id: 'LUK', name: 'Luke Skywalker', force: true, rank: 'Commander' },
|
|
44
45
|
{ id: 'HAN', name: 'Han Solo', force: false }
|
|
45
|
-
]
|
|
46
|
+
],
|
|
47
|
+
weapons: ['Lightsaber', 'Blaster', 'Bowcaster']
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const diffs = diff(oldData, newData, { characters: 'id' });
|
|
@@ -116,7 +118,13 @@ const expectedDiffs = [
|
|
|
116
118
|
Paths can be utilized to identify keys within nested arrays.
|
|
117
119
|
|
|
118
120
|
```javascript
|
|
119
|
-
const diffs = diff(oldData, newData, { characters.subarray: 'id' });
|
|
121
|
+
const diffs = diff(oldData, newData, { 'characters.subarray': 'id' });
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
You can also designate the root by using '.' instead of an empty string ('').
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const diffs = diff(oldData, newData, { '.characters.subarray': 'id' });
|
|
120
128
|
```
|
|
121
129
|
|
|
122
130
|
You can use a function to dynamically resolve the key of the object.
|
|
@@ -138,6 +146,12 @@ embeddedObjKeys.set(/^char\w+$/, 'id'); // instead of 'id' you can specify a fun
|
|
|
138
146
|
const diffs = diff(oldObj, newObj, embeddedObjKeys);
|
|
139
147
|
```
|
|
140
148
|
|
|
149
|
+
Compare string arrays by value instead of index
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const diffs = diff(oldObj, newObj, { stringArr: '$value' });
|
|
153
|
+
```
|
|
154
|
+
|
|
141
155
|
### `flattenChangeset`
|
|
142
156
|
|
|
143
157
|
Transforms a complex changeset into a flat list of atomic changes, each describable by a JSONPath.
|
|
@@ -238,6 +252,8 @@ Discover more about the company behind this project: [hololux](https://hololux.c
|
|
|
238
252
|
|
|
239
253
|
## Release Notes
|
|
240
254
|
|
|
255
|
+
- **v2.2.0:** Fix lodash-es decependency, exclude keys, compare string arrays by value
|
|
256
|
+
- **v2.1.0:** Resolves a problem related to JSON Path filters by replacing the single equal sign (=) with a double equal sign (==). This update maintains compatibility with existing flat changes. Allows to use either '' or '.' as root in the path.
|
|
241
257
|
- **v2.0.0:** json-diff-ts has been upgraded to an ECMAScript module! This major update brings optimizations and enhanced documentation. Additionally, a previously existing issue where all paths were treated as regex has been fixed. In this new version, you'll need to use a Map instead of a Record for regex paths. Please note that this is a breaking change if you were using regex paths in the previous versions.
|
|
242
258
|
- **v1.2.6:** Enhanced JSON Path handling for period-inclusive segments.
|
|
243
259
|
- **v1.2.5:** Patched dependencies; added key name resolution support for key functions.
|
package/lib/jsonDiff.d.ts
CHANGED
|
@@ -23,7 +23,15 @@ export interface IFlatChange {
|
|
|
23
23
|
value?: any;
|
|
24
24
|
oldValue?: any;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Computes the difference between two objects.
|
|
28
|
+
*
|
|
29
|
+
* @param {any} oldObj - The original object.
|
|
30
|
+
* @param {any} newObj - The updated object.
|
|
31
|
+
* @param {EmbeddedObjKeysType | EmbeddedObjKeysMapType} embeddedObjKeys - An optional parameter specifying keys of embedded objects.
|
|
32
|
+
* @returns {IChange[]} - An array of changes that transform the old object into the new object.
|
|
33
|
+
*/
|
|
34
|
+
export declare function diff(oldObj: any, newObj: any, embeddedObjKeys?: EmbeddedObjKeysType | EmbeddedObjKeysMapType, keysToSkip?: string[]): IChange[];
|
|
27
35
|
/**
|
|
28
36
|
* Applies all changes in the changeset to the object.
|
|
29
37
|
*
|
package/lib/jsonDiff.js
CHANGED
|
@@ -5,8 +5,27 @@ export var Operation;
|
|
|
5
5
|
Operation["ADD"] = "ADD";
|
|
6
6
|
Operation["UPDATE"] = "UPDATE";
|
|
7
7
|
})(Operation || (Operation = {}));
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Computes the difference between two objects.
|
|
10
|
+
*
|
|
11
|
+
* @param {any} oldObj - The original object.
|
|
12
|
+
* @param {any} newObj - The updated object.
|
|
13
|
+
* @param {EmbeddedObjKeysType | EmbeddedObjKeysMapType} embeddedObjKeys - An optional parameter specifying keys of embedded objects.
|
|
14
|
+
* @returns {IChange[]} - An array of changes that transform the old object into the new object.
|
|
15
|
+
*/
|
|
16
|
+
export function diff(oldObj, newObj, embeddedObjKeys, keysToSkip) {
|
|
17
|
+
// Trim leading '.' from keys in embeddedObjKeys
|
|
18
|
+
if (embeddedObjKeys instanceof Map) {
|
|
19
|
+
embeddedObjKeys = new Map(Array.from(embeddedObjKeys.entries()).map(([key, value]) => [
|
|
20
|
+
key instanceof RegExp ? key : key.replace(/^\./, ''),
|
|
21
|
+
value
|
|
22
|
+
]));
|
|
23
|
+
}
|
|
24
|
+
else if (embeddedObjKeys) {
|
|
25
|
+
embeddedObjKeys = Object.fromEntries(Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ''), value]));
|
|
26
|
+
}
|
|
27
|
+
// Compare old and new objects to generate a list of changes
|
|
28
|
+
return compare(oldObj, newObj, [], embeddedObjKeys, [], keysToSkip);
|
|
10
29
|
}
|
|
11
30
|
/**
|
|
12
31
|
* Applies all changes in the changeset to the object.
|
|
@@ -73,13 +92,31 @@ export const flattenChangeset = (obj, path = '$', embeddedKey) => {
|
|
|
73
92
|
}
|
|
74
93
|
else {
|
|
75
94
|
if (obj.changes || embeddedKey) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
if (embeddedKey) {
|
|
96
|
+
if (embeddedKey === '$index') {
|
|
97
|
+
path = `${path}[${obj.key}]`;
|
|
98
|
+
}
|
|
99
|
+
else if (embeddedKey === '$value') {
|
|
100
|
+
path = `${path}[?(@='${obj.key}')]`;
|
|
101
|
+
const valueType = getTypeOfObj(obj.value);
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
...obj,
|
|
105
|
+
path,
|
|
106
|
+
valueType
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
else if (obj.type === Operation.ADD) {
|
|
111
|
+
// do nothing
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
path = filterExpression(path, embeddedKey, obj.key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
path = append(path, obj.key);
|
|
119
|
+
}
|
|
83
120
|
return flattenChangeset(obj.changes || obj, path, obj.embeddedKey);
|
|
84
121
|
}
|
|
85
122
|
else {
|
|
@@ -115,14 +152,7 @@ export const unflattenChanges = (changes) => {
|
|
|
115
152
|
changes.forEach((change) => {
|
|
116
153
|
const obj = {};
|
|
117
154
|
let ptr = obj;
|
|
118
|
-
const segments = change.path.split(/([^@])
|
|
119
|
-
const x = Math.floor(i / 2);
|
|
120
|
-
if (!acc[x]) {
|
|
121
|
-
acc[x] = '';
|
|
122
|
-
}
|
|
123
|
-
acc[x] += curr;
|
|
124
|
-
return acc;
|
|
125
|
-
}, []);
|
|
155
|
+
const segments = change.path.split(/(?<=[^@])\.(?=[^@])/);
|
|
126
156
|
if (segments.length === 1) {
|
|
127
157
|
ptr.key = change.key;
|
|
128
158
|
ptr.type = change.type;
|
|
@@ -133,8 +163,8 @@ export const unflattenChanges = (changes) => {
|
|
|
133
163
|
else {
|
|
134
164
|
for (let i = 1; i < segments.length; i++) {
|
|
135
165
|
const segment = segments[i];
|
|
136
|
-
// Matches JSONPath segments: "items[?(@.id
|
|
137
|
-
const result = /^(
|
|
166
|
+
// Matches JSONPath segments: "items[?(@.id=='123')]", "items[?(@.id==123)]", "items[2]", "items[?(@='123')]"
|
|
167
|
+
const result = /^(.+?)\[\?\(@.?(?:([^=]*))?={1,2}'(.*)'\)\]$|^(.+?)\[(\d+)\]$/.exec(segment); //NOSONAR
|
|
138
168
|
// array
|
|
139
169
|
if (result) {
|
|
140
170
|
let key;
|
|
@@ -142,13 +172,13 @@ export const unflattenChanges = (changes) => {
|
|
|
142
172
|
let arrKey;
|
|
143
173
|
if (result[1]) {
|
|
144
174
|
key = result[1];
|
|
145
|
-
embeddedKey = result[2];
|
|
175
|
+
embeddedKey = result[2] || '$value';
|
|
146
176
|
arrKey = result[3];
|
|
147
177
|
}
|
|
148
178
|
else {
|
|
149
179
|
key = result[4];
|
|
150
180
|
embeddedKey = '$index';
|
|
151
|
-
arrKey = Number(result[
|
|
181
|
+
arrKey = Number(result[4]);
|
|
152
182
|
}
|
|
153
183
|
// leaf
|
|
154
184
|
if (i === segments.length - 1) {
|
|
@@ -241,7 +271,7 @@ const getKey = (path) => {
|
|
|
241
271
|
const left = path[path.length - 1];
|
|
242
272
|
return left != null ? left : '$root';
|
|
243
273
|
};
|
|
244
|
-
const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
|
|
274
|
+
const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip) => {
|
|
245
275
|
let changes = [];
|
|
246
276
|
const typeOfOldObj = getTypeOfObj(oldObj);
|
|
247
277
|
const typeOfNewObj = getTypeOfObj(newObj);
|
|
@@ -260,7 +290,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
|
|
|
260
290
|
})));
|
|
261
291
|
break;
|
|
262
292
|
case 'Object':
|
|
263
|
-
const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath);
|
|
293
|
+
const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath, false, keysToSkip);
|
|
264
294
|
if (diffs.length) {
|
|
265
295
|
if (path.length) {
|
|
266
296
|
changes.push({
|
|
@@ -275,7 +305,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
|
|
|
275
305
|
}
|
|
276
306
|
break;
|
|
277
307
|
case 'Array':
|
|
278
|
-
changes = changes.concat(compareArray(oldObj, newObj, path, embeddedObjKeys, keyPath));
|
|
308
|
+
changes = changes.concat(compareArray(oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip));
|
|
279
309
|
break;
|
|
280
310
|
case 'Function':
|
|
281
311
|
break;
|
|
@@ -285,7 +315,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
|
|
|
285
315
|
}
|
|
286
316
|
return changes;
|
|
287
317
|
};
|
|
288
|
-
const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath = false) => {
|
|
318
|
+
const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath = false, keysToSkip = []) => {
|
|
289
319
|
let k;
|
|
290
320
|
let newKeyPath;
|
|
291
321
|
let newPath;
|
|
@@ -293,13 +323,13 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
|
|
|
293
323
|
skipPath = false;
|
|
294
324
|
}
|
|
295
325
|
let changes = [];
|
|
296
|
-
const oldObjKeys = Object.keys(oldObj);
|
|
297
|
-
const newObjKeys = Object.keys(newObj);
|
|
326
|
+
const oldObjKeys = Object.keys(oldObj).filter((key) => keysToSkip.indexOf(key) === -1);
|
|
327
|
+
const newObjKeys = Object.keys(newObj).filter((key) => keysToSkip.indexOf(key) === -1);
|
|
298
328
|
const intersectionKeys = intersection(oldObjKeys, newObjKeys);
|
|
299
329
|
for (k of intersectionKeys) {
|
|
300
330
|
newPath = path.concat([k]);
|
|
301
331
|
newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
|
|
302
|
-
const diffs = compare(oldObj[k], newObj[k], newPath, embeddedObjKeys, newKeyPath);
|
|
332
|
+
const diffs = compare(oldObj[k], newObj[k], newPath, embeddedObjKeys, newKeyPath, keysToSkip);
|
|
303
333
|
if (diffs.length) {
|
|
304
334
|
changes = changes.concat(diffs);
|
|
305
335
|
}
|
|
@@ -326,12 +356,12 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
|
|
|
326
356
|
}
|
|
327
357
|
return changes;
|
|
328
358
|
};
|
|
329
|
-
const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
|
|
359
|
+
const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip) => {
|
|
330
360
|
const left = getObjectKey(embeddedObjKeys, keyPath);
|
|
331
361
|
const uniqKey = left != null ? left : '$index';
|
|
332
362
|
const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
|
|
333
363
|
const indexedNewObj = convertArrayToObj(newObj, uniqKey);
|
|
334
|
-
const diffs = compareObject(indexedOldObj, indexedNewObj, path, embeddedObjKeys, keyPath, true);
|
|
364
|
+
const diffs = compareObject(indexedOldObj, indexedNewObj, path, embeddedObjKeys, keyPath, true, keysToSkip);
|
|
335
365
|
if (diffs.length) {
|
|
336
366
|
return [
|
|
337
367
|
{
|
|
@@ -370,7 +400,12 @@ const getObjectKey = (embeddedObjKeys, keyPath) => {
|
|
|
370
400
|
};
|
|
371
401
|
const convertArrayToObj = (arr, uniqKey) => {
|
|
372
402
|
let obj = {};
|
|
373
|
-
if (uniqKey
|
|
403
|
+
if (uniqKey === '$value') {
|
|
404
|
+
arr.forEach((value) => {
|
|
405
|
+
obj[value] = value;
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
else if (uniqKey !== '$index') {
|
|
374
409
|
obj = keyBy(arr, uniqKey);
|
|
375
410
|
}
|
|
376
411
|
else {
|
|
@@ -414,6 +449,9 @@ const removeKey = (obj, key, embeddedKey) => {
|
|
|
414
449
|
}
|
|
415
450
|
};
|
|
416
451
|
const indexOfItemInArray = (arr, key, value) => {
|
|
452
|
+
if (key === '$value') {
|
|
453
|
+
return arr.indexOf(value);
|
|
454
|
+
}
|
|
417
455
|
for (let i = 0; i < arr.length; i++) {
|
|
418
456
|
const item = arr[i];
|
|
419
457
|
if (item && item[key] ? item[key].toString() === value.toString() : undefined) {
|
|
@@ -453,8 +491,14 @@ const applyArrayChange = (arr, change) => (() => {
|
|
|
453
491
|
if (change.embeddedKey === '$index') {
|
|
454
492
|
element = arr[subchange.key];
|
|
455
493
|
}
|
|
494
|
+
else if (change.embeddedKey === '$value') {
|
|
495
|
+
const index = arr.indexOf(subchange.key);
|
|
496
|
+
if (index !== -1) {
|
|
497
|
+
element = arr[index];
|
|
498
|
+
}
|
|
499
|
+
}
|
|
456
500
|
else {
|
|
457
|
-
element = find(arr, (el) => el[change.embeddedKey]
|
|
501
|
+
element = find(arr, (el) => el[change.embeddedKey]?.toString() === subchange.key.toString());
|
|
458
502
|
}
|
|
459
503
|
result.push(applyChangeset(element, subchange.changes));
|
|
460
504
|
}
|
|
@@ -513,7 +557,8 @@ function append(basePath, nextSegment) {
|
|
|
513
557
|
}
|
|
514
558
|
/** returns a JSON Path filter expression; e.g., `$.pet[(?name='spot')]` */
|
|
515
559
|
function filterExpression(basePath, filterKey, filterValue) {
|
|
560
|
+
const value = typeof filterValue === 'number' ? filterValue : `'${filterValue}'`;
|
|
516
561
|
return typeof filterKey === 'string' && filterKey.includes('.')
|
|
517
|
-
? `${basePath}[?(@[${filterKey}]
|
|
518
|
-
: `${basePath}[?(@.${filterKey}
|
|
562
|
+
? `${basePath}[?(@[${filterKey}]==${value})]`
|
|
563
|
+
: `${basePath}[?(@.${filterKey}==${value})]`;
|
|
519
564
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-diff-ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A JSON diff tool for JavaScript written in TypeScript.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
11
11
|
"lint": "eslint 'src/**/*.ts'",
|
|
12
|
-
"test": "NODE_OPTIONS=--experimental-vm-modules
|
|
13
|
-
"test:watch": "NODE_OPTIONS=--experimental-vm-modules
|
|
12
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.mjs",
|
|
13
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --config jest.config.mjs",
|
|
14
14
|
"prepare": "npm run build",
|
|
15
15
|
"prepublishOnly": "npm test && npm run lint",
|
|
16
16
|
"preversion": "npm run lint",
|
|
@@ -37,15 +37,17 @@
|
|
|
37
37
|
"url": "https://github.com/ltwlf/json-diff-ts/issues"
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/ltwlf/json-diff-ts#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"lodash-es": "^4.17.21"
|
|
42
|
+
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@jest/globals": "^29.7.0",
|
|
42
45
|
"@types/jest": "^29.5.7",
|
|
43
46
|
"@types/lodash-es": "^4.17.10",
|
|
44
47
|
"eslint": "^8.53.0",
|
|
45
|
-
"jest": "^29.
|
|
46
|
-
"lodash-es": "^4.17.21",
|
|
48
|
+
"jest": "^29.7.0",
|
|
47
49
|
"prettier": "^3.0.3",
|
|
48
50
|
"ts-jest": "^29.0.5",
|
|
49
|
-
"typescript": "^
|
|
51
|
+
"typescript": "^5.3.2"
|
|
50
52
|
}
|
|
51
53
|
}
|