json-diff-ts 2.1.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 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' });
@@ -115,12 +117,6 @@ const expectedDiffs = [
115
117
 
116
118
  Paths can be utilized to identify keys within nested arrays.
117
119
 
118
- ```javascript
119
- const diffs = diff(oldData, newData, { characters.subarray: 'id' });
120
- ```
121
-
122
- Alternative Syntax
123
-
124
120
  ```javascript
125
121
  const diffs = diff(oldData, newData, { 'characters.subarray': 'id' });
126
122
  ```
@@ -150,6 +146,12 @@ embeddedObjKeys.set(/^char\w+$/, 'id'); // instead of 'id' you can specify a fun
150
146
  const diffs = diff(oldObj, newObj, embeddedObjKeys);
151
147
  ```
152
148
 
149
+ Compare string arrays by value instead of index
150
+
151
+ ```javascript
152
+ const diffs = diff(oldObj, newObj, { stringArr: '$value' });
153
+ ```
154
+
153
155
  ### `flattenChangeset`
154
156
 
155
157
  Transforms a complex changeset into a flat list of atomic changes, each describable by a JSONPath.
@@ -250,6 +252,7 @@ Discover more about the company behind this project: [hololux](https://hololux.c
250
252
 
251
253
  ## Release Notes
252
254
 
255
+ - **v2.2.0:** Fix lodash-es decependency, exclude keys, compare string arrays by value
253
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.
254
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.
255
258
  - **v1.2.6:** Enhanced JSON Path handling for period-inclusive segments.
package/lib/jsonDiff.d.ts CHANGED
@@ -31,7 +31,7 @@ export interface IFlatChange {
31
31
  * @param {EmbeddedObjKeysType | EmbeddedObjKeysMapType} embeddedObjKeys - An optional parameter specifying keys of embedded objects.
32
32
  * @returns {IChange[]} - An array of changes that transform the old object into the new object.
33
33
  */
34
- export declare function diff(oldObj: any, newObj: any, embeddedObjKeys?: EmbeddedObjKeysType | EmbeddedObjKeysMapType): IChange[];
34
+ export declare function diff(oldObj: any, newObj: any, embeddedObjKeys?: EmbeddedObjKeysType | EmbeddedObjKeysMapType, keysToSkip?: string[]): IChange[];
35
35
  /**
36
36
  * Applies all changes in the changeset to the object.
37
37
  *
package/lib/jsonDiff.js CHANGED
@@ -13,7 +13,7 @@ export var Operation;
13
13
  * @param {EmbeddedObjKeysType | EmbeddedObjKeysMapType} embeddedObjKeys - An optional parameter specifying keys of embedded objects.
14
14
  * @returns {IChange[]} - An array of changes that transform the old object into the new object.
15
15
  */
16
- export function diff(oldObj, newObj, embeddedObjKeys) {
16
+ export function diff(oldObj, newObj, embeddedObjKeys, keysToSkip) {
17
17
  // Trim leading '.' from keys in embeddedObjKeys
18
18
  if (embeddedObjKeys instanceof Map) {
19
19
  embeddedObjKeys = new Map(Array.from(embeddedObjKeys.entries()).map(([key, value]) => [
@@ -25,7 +25,7 @@ export function diff(oldObj, newObj, embeddedObjKeys) {
25
25
  embeddedObjKeys = Object.fromEntries(Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ''), value]));
26
26
  }
27
27
  // Compare old and new objects to generate a list of changes
28
- return compare(oldObj, newObj, [], embeddedObjKeys, []);
28
+ return compare(oldObj, newObj, [], embeddedObjKeys, [], keysToSkip);
29
29
  }
30
30
  /**
31
31
  * Applies all changes in the changeset to the object.
@@ -92,13 +92,31 @@ export const flattenChangeset = (obj, path = '$', embeddedKey) => {
92
92
  }
93
93
  else {
94
94
  if (obj.changes || embeddedKey) {
95
- path = embeddedKey
96
- ? embeddedKey === '$index'
97
- ? `${path}[${obj.key}]`
98
- : obj.type === Operation.ADD
99
- ? path
100
- : filterExpression(path, embeddedKey, obj.key)
101
- : (path = append(path, obj.key));
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
+ }
102
120
  return flattenChangeset(obj.changes || obj, path, obj.embeddedKey);
103
121
  }
104
122
  else {
@@ -145,8 +163,8 @@ export const unflattenChanges = (changes) => {
145
163
  else {
146
164
  for (let i = 1; i < segments.length; i++) {
147
165
  const segment = segments[i];
148
- // Matches JSONPath segments: "items[?(@.id=='123')]", items[?(@.id==123)], "items[2]"
149
- const result = /^(.+)\[\?\(@\.([^=]+)={1,2}(?:'(.*)'|(\d+))\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
166
+ // Matches JSONPath segments: "items[?(@.id=='123')]", "items[?(@.id==123)]", "items[2]", "items[?(@='123')]"
167
+ const result = /^(.+?)\[\?\(@.?(?:([^=]*))?={1,2}'(.*)'\)\]$|^(.+?)\[(\d+)\]$/.exec(segment); //NOSONAR
150
168
  // array
151
169
  if (result) {
152
170
  let key;
@@ -154,13 +172,13 @@ export const unflattenChanges = (changes) => {
154
172
  let arrKey;
155
173
  if (result[1]) {
156
174
  key = result[1];
157
- embeddedKey = result[2];
175
+ embeddedKey = result[2] || '$value';
158
176
  arrKey = result[3];
159
177
  }
160
178
  else {
161
- key = result[5];
179
+ key = result[4];
162
180
  embeddedKey = '$index';
163
- arrKey = Number(result[6]);
181
+ arrKey = Number(result[4]);
164
182
  }
165
183
  // leaf
166
184
  if (i === segments.length - 1) {
@@ -253,7 +271,7 @@ const getKey = (path) => {
253
271
  const left = path[path.length - 1];
254
272
  return left != null ? left : '$root';
255
273
  };
256
- const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
274
+ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip) => {
257
275
  let changes = [];
258
276
  const typeOfOldObj = getTypeOfObj(oldObj);
259
277
  const typeOfNewObj = getTypeOfObj(newObj);
@@ -272,7 +290,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
272
290
  })));
273
291
  break;
274
292
  case 'Object':
275
- const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath);
293
+ const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath, false, keysToSkip);
276
294
  if (diffs.length) {
277
295
  if (path.length) {
278
296
  changes.push({
@@ -287,7 +305,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
287
305
  }
288
306
  break;
289
307
  case 'Array':
290
- changes = changes.concat(compareArray(oldObj, newObj, path, embeddedObjKeys, keyPath));
308
+ changes = changes.concat(compareArray(oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip));
291
309
  break;
292
310
  case 'Function':
293
311
  break;
@@ -297,7 +315,7 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
297
315
  }
298
316
  return changes;
299
317
  };
300
- const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath = false) => {
318
+ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath = false, keysToSkip = []) => {
301
319
  let k;
302
320
  let newKeyPath;
303
321
  let newPath;
@@ -305,13 +323,13 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
305
323
  skipPath = false;
306
324
  }
307
325
  let changes = [];
308
- const oldObjKeys = Object.keys(oldObj);
309
- 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);
310
328
  const intersectionKeys = intersection(oldObjKeys, newObjKeys);
311
329
  for (k of intersectionKeys) {
312
330
  newPath = path.concat([k]);
313
331
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
314
- const diffs = compare(oldObj[k], newObj[k], newPath, embeddedObjKeys, newKeyPath);
332
+ const diffs = compare(oldObj[k], newObj[k], newPath, embeddedObjKeys, newKeyPath, keysToSkip);
315
333
  if (diffs.length) {
316
334
  changes = changes.concat(diffs);
317
335
  }
@@ -338,12 +356,12 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
338
356
  }
339
357
  return changes;
340
358
  };
341
- const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
359
+ const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath, keysToSkip) => {
342
360
  const left = getObjectKey(embeddedObjKeys, keyPath);
343
361
  const uniqKey = left != null ? left : '$index';
344
362
  const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
345
363
  const indexedNewObj = convertArrayToObj(newObj, uniqKey);
346
- const diffs = compareObject(indexedOldObj, indexedNewObj, path, embeddedObjKeys, keyPath, true);
364
+ const diffs = compareObject(indexedOldObj, indexedNewObj, path, embeddedObjKeys, keyPath, true, keysToSkip);
347
365
  if (diffs.length) {
348
366
  return [
349
367
  {
@@ -382,7 +400,12 @@ const getObjectKey = (embeddedObjKeys, keyPath) => {
382
400
  };
383
401
  const convertArrayToObj = (arr, uniqKey) => {
384
402
  let obj = {};
385
- if (uniqKey !== '$index') {
403
+ if (uniqKey === '$value') {
404
+ arr.forEach((value) => {
405
+ obj[value] = value;
406
+ });
407
+ }
408
+ else if (uniqKey !== '$index') {
386
409
  obj = keyBy(arr, uniqKey);
387
410
  }
388
411
  else {
@@ -426,6 +449,9 @@ const removeKey = (obj, key, embeddedKey) => {
426
449
  }
427
450
  };
428
451
  const indexOfItemInArray = (arr, key, value) => {
452
+ if (key === '$value') {
453
+ return arr.indexOf(value);
454
+ }
429
455
  for (let i = 0; i < arr.length; i++) {
430
456
  const item = arr[i];
431
457
  if (item && item[key] ? item[key].toString() === value.toString() : undefined) {
@@ -465,6 +491,12 @@ const applyArrayChange = (arr, change) => (() => {
465
491
  if (change.embeddedKey === '$index') {
466
492
  element = arr[subchange.key];
467
493
  }
494
+ else if (change.embeddedKey === '$value') {
495
+ const index = arr.indexOf(subchange.key);
496
+ if (index !== -1) {
497
+ element = arr[index];
498
+ }
499
+ }
468
500
  else {
469
501
  element = find(arr, (el) => el[change.embeddedKey]?.toString() === subchange.key.toString());
470
502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-diff-ts",
3
- "version": "2.1.0",
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 npx jest --config jest.config.mjs",
13
- "test:watch": "NODE_OPTIONS=--experimental-vm-modules npx jest --watch --config jest.config.mjs",
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.5.0",
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": "^4.9.5"
51
+ "typescript": "^5.3.2"
50
52
  }
51
53
  }