json-diff-ts 1.2.6 → 2.0.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
@@ -4,315 +4,253 @@
4
4
  [![Known Vulnerabilities](https://snyk.io/test/github/ltwlf/json-diff-ts/badge.svg?targetFile=package.json)](https://snyk.io/test/github/ltwlf/json-diff-ts?targetFile=package.json)
5
5
  [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ltwlf_json-diff-ts&metric=alert_status)](https://sonarcloud.io/dashboard?id=ltwlf_json-diff-ts)
6
6
 
7
- TypeScript diff tool with support for array keys instead of just indexes and compatible with JSONPath.
7
+ `json-diff-ts` is a TypeScript library that calculates and applies differences between JSON objects. A standout feature is its ability to identify elements in arrays using keys instead of indices, which offers a more intuitive way to handle arrays. It also supports JSONPath, a query language for JSON, which enables you to target specific parts of a JSON document with precision.
8
8
 
9
- ## Features
9
+ Another significant feature of this library is its ability to transform changesets into atomic changes. This means that each change in the data can be isolated and applied independently, providing a granular level of control over the data manipulation process.
10
10
 
11
- ### diff
11
+ This library is particularly valuable for applications where tracking changes in JSON data is crucial. It simplifies the process of comparing JSON objects and applying changes. The support for key-based array identification can be especially useful in complex JSON structures where tracking by index is not efficient or intuitive. JSONPath support further enhances its capabilities by allowing precise targeting of specific parts in a JSON document, making it a versatile tool for handling JSON data.
12
12
 
13
- If a embedded key is specified for an array, the diff will be generated based on the objects with the same keys.
13
+ ## Installation
14
14
 
15
- #### Examples:
15
+ ```sh
16
+ npm install json-diff-ts
17
+ ```
18
+
19
+ ## Capabilities
20
+
21
+ ### `diff`
22
+
23
+ Generates a difference set for JSON objects. When comparing arrays, if a specific key is provided, differences are determined by matching elements via this key rather than array indices.
24
+
25
+ #### Examples using Star Wars data:
16
26
 
17
27
  ```javascript
18
- var changesets = require('json-diff-ts');
19
- var newObj, oldObj;
20
-
21
- oldObj = {
22
- name: 'joe',
23
- age: 55,
24
- coins: [2, 5],
25
- children: [
26
- { name: 'kid1', age: 1 },
27
- { name: 'kid2', age: 2 }
28
+ import { diff } from 'json-diff-ts';
29
+
30
+ const oldData = {
31
+ planet: 'Tatooine',
32
+ faction: 'Jedi',
33
+ characters: [
34
+ { id: 'LUK', name: 'Luke Skywalker', force: true },
35
+ { id: 'LEI', name: 'Leia Organa', force: true }
28
36
  ]
29
37
  };
30
38
 
31
- newObj = {
32
- name: 'smith',
33
- coins: [2, 5, 1],
34
- children: [
35
- { name: 'kid3', age: 3 },
36
- { name: 'kid1', age: 0 },
37
- { name: 'kid2', age: 2 }
39
+ const newData = {
40
+ planet: 'Alderaan',
41
+ faction: 'Rebel Alliance',
42
+ characters: [
43
+ { id: 'LUK', name: 'Luke Skywalker', force: true, rank: 'Commander' },
44
+ { id: 'HAN', name: 'Han Solo', force: false }
38
45
  ]
39
46
  };
40
47
 
41
- // Assume children is an array of child object and the child object has 'name' as its primary key
42
- // keys can also be hierarchical e.g. {children: 'name', 'children.grandChildren', 'age'}
43
- // or use functions that return the key of an object e.g. {children: function(obj) { return obj.key; }}
44
- // when you use a function flatten can not generate the correct path.
45
- // to fix this, you can add an additional parameter e.g. (obj, getKeyNameFlag) => {...}. getKeyNameFlag will be true when the diff library try to resolve the key name instead of the key value. You can return a static string or use obj to check which key name you should return. obj will be the first object of the array!
46
- diffs = changesets.diff(oldObj, newObj, { children: 'name' });
48
+ const diffs = diff(oldData, newData, { characters: 'id' });
47
49
 
48
- expect(diffs).to.eql([
50
+ const expectedDiffs = [
49
51
  {
50
- type: 'update',
51
- key: 'name',
52
- value: 'smith',
53
- oldValue: 'joe'
52
+ type: 'UPDATE',
53
+ key: 'planet',
54
+ value: 'Alderaan',
55
+ oldValue: 'Tatooine'
54
56
  },
55
57
  {
56
- type: 'update',
57
- key: 'coins',
58
- embededKey: '$index',
59
- changes: [{ type: 'add', key: '2', value: 1 }]
58
+ type: 'UPDATE',
59
+ key: 'faction',
60
+ value: 'Rebel Alliance',
61
+ oldValue: 'Jedi'
60
62
  },
61
63
  {
62
- type: 'update',
63
- key: 'children',
64
- embededKey: 'name',
64
+ type: 'UPDATE',
65
+ key: 'characters',
66
+ embeddedKey: 'id',
65
67
  changes: [
66
68
  {
67
- type: 'update',
68
- key: 'kid1',
69
- changes: [{ type: 'update', key: 'age', value: 0, oldValue: 1 }]
69
+ type: 'UPDATE',
70
+ key: 'LUK',
71
+ changes: [
72
+ {
73
+ type: 'ADD',
74
+ key: 'rank',
75
+ value: 'Commander'
76
+ }
77
+ ]
70
78
  },
71
79
  {
72
- type: 'add',
73
- key: 'kid3',
74
- value: { name: 'kid3', age: 3 }
80
+ type: 'ADD',
81
+ key: 'HAN',
82
+ value: {
83
+ id: 'HAN',
84
+ name: 'Han Solo',
85
+ force: false
86
+ }
87
+ },
88
+ {
89
+ type: 'REMOVE',
90
+ key: 'LEI',
91
+ value: {
92
+ id: 'LEI',
93
+ name: 'Leia Organa',
94
+ force: true
95
+ }
75
96
  }
76
97
  ]
77
98
  },
78
99
  {
79
- type: 'remove',
80
- key: 'age',
81
- value: 55
100
+ type: 'UPDATE',
101
+ key: 'weapons',
102
+ embeddedKey: '$index',
103
+ changes: [
104
+ {
105
+ type: 'ADD',
106
+ key: '2',
107
+ value: 'Bowcaster'
108
+ }
109
+ ]
82
110
  }
83
- ]);
111
+ ];
84
112
  ```
85
113
 
86
- ### flattenChangeset
114
+ #### Advanced
87
115
 
88
- Converts the changeset into a flat atomic change list compatible with JSONPath.
116
+ Paths can be utilized to identify keys within nested arrays.
117
+
118
+ ```javascript
119
+ const diffs = diff(oldData, newData, { characters.subarray: 'id' });
120
+ ```
121
+
122
+ You can use a function to dynamically resolve the key of the object.
123
+ The first parameter is the object and the second is to signal if the function should return the key name instead of the value. This is needed to flatten the changeset
124
+
125
+ ```javascript
126
+ const diffs = diff(oldData, newData, {
127
+ characters: (obj, shouldReturnKeyName) => (shouldReturnKeyName ? 'id' : obj.id)
128
+ });
129
+ ```
130
+
131
+ If you're using the Map type, you can employ regular expressions for path identification.
132
+
133
+ ```javascript
134
+ const embeddedObjKeys: EmbeddedObjKeysMapType = new Map();
135
+
136
+ embeddedObjKeys.set(/^char\w+$/, 'id'); // instead of 'id' you can specify a function
137
+
138
+ const diffs = diff(oldObj, newObj, embeddedObjKeys);
139
+ ```
140
+
141
+ ### `flattenChangeset`
142
+
143
+ Transforms a complex changeset into a flat list of atomic changes, each describable by a JSONPath.
89
144
 
90
145
  #### Examples:
91
146
 
92
147
  ```javascript
93
148
  const flatChanges = flattenChangeset(diffs);
94
- // convert changes back to changeset format
95
- const changeset = unflattenChanges(flatChanges.slice(1, 5));
96
- // or use a JSONPath library to apply the patches
149
+ // Restore the changeset from a selection of flat changes
150
+ const changeset = unflattenChanges(flatChanges.slice(0, 3));
151
+ // Alternatively, apply the changes using a JSONPath-capable library
97
152
  // ...
98
153
  ```
99
154
 
100
- The **flatChange** format will look like this:
155
+ A **flatChange** will have the following structure:
101
156
 
102
157
  ```javascript
103
158
  [
104
- { type: 'UPDATE', key: 'name', value: 'smith', oldValue: 'joe', path: '$.name', valueType: 'String' },
105
- { type: 'REMOVE', key: 'mixed', value: 10, path: '$.mixed', valueType: 'Number' },
106
- { type: 'UPDATE', key: 'inner', value: 2, oldValue: 1, path: '$.nested.inner', valueType: 'Number' },
107
- {
108
- type: 'UPDATE',
109
- key: 'date',
110
- value: '2014-10-12T09:13:00.000Z',
111
- oldValue: '2014-10-13T09:13:00.000Z',
112
- path: '$.date',
113
- valueType: 'Date'
114
- },
115
- { type: 'ADD', key: '2', value: 1, path: '$.coins[2]', valueType: 'Number' },
116
- { type: 'REMOVE', key: '0', value: 'car', path: '$.toys[0]', valueType: 'String' },
117
- { type: 'REMOVE', key: '1', value: 'doll', path: '$.toys[1]', valueType: 'String' },
118
- { type: 'REMOVE', key: '0', path: '$.pets[0]', valueType: 'undefined' },
119
- { type: 'REMOVE', key: '1', value: null, path: '$.pets[1]', valueType: null },
120
- { type: 'UPDATE', key: 'age', value: 0, oldValue: 1, path: "$.children[?(@.name='kid1')].age", valueType: 'Number' },
121
- {
122
- type: 'UPDATE',
123
- key: 'value',
124
- value: 'heihei',
125
- oldValue: 'haha',
126
- path: "$.children[?(@.name='kid1')].subset[?(@.id='1')].value",
127
- valueType: 'String'
128
- },
129
- {
130
- type: 'REMOVE',
131
- key: '2',
132
- value: { id: 2, value: 'hehe' },
133
- path: "$.children[?(@.name='kid1')].subset[?(@.id='2')]",
134
- valueType: 'Object'
135
- },
136
- { type: 'ADD', key: 'kid3', value: { name: 'kid3', age: 3 }, path: '$.children', valueType: 'Object' }
159
+ { type: 'UPDATE', key: 'planet', value: 'Alderaan', oldValue: 'Tatooine', path: '$.planet', valueType: 'String' },
160
+ // ... Additional flat changes here
161
+ { type: 'ADD', key: 'rank', value: 'Commander', path: "$.characters[?(@.id=='LUK')].rank", valueType: 'String' }
137
162
  ];
138
163
  ```
139
164
 
140
- ### applyChange
165
+ ### `applyChange`
141
166
 
142
167
  #### Examples:
143
168
 
144
169
  ```javascript
145
- var changesets = require('json-diff-ts');
146
- var oldObj = {
147
- name: 'joe',
148
- age: 55,
149
- coins: [2, 5],
150
- children: [
151
- { name: 'kid1', age: 1 },
152
- { name: 'kid2', age: 2 }
153
- ]
170
+ const oldData = {
171
+ // ... Initial data here
154
172
  };
155
173
 
156
- // Assume children is an array of child object and the child object has 'name' as its primary key
157
- diffs = [
158
- {
159
- type: 'update',
160
- key: 'name',
161
- value: 'smith',
162
- oldValue: 'joe'
163
- },
164
- {
165
- type: 'update',
166
- key: 'coins',
167
- embededKey: '$index',
168
- changes: [{ type: 'add', key: '2', value: 1 }]
169
- },
170
- {
171
- type: 'update',
172
- key: 'children',
173
- embededKey: 'name', // The key property name of the elements in an array
174
- changes: [
175
- {
176
- type: 'update',
177
- key: 'kid1',
178
- changes: [{ type: 'update', key: 'age', value: 0, oldValue: 1 }]
179
- },
180
- {
181
- type: 'add',
182
- key: 'kid3',
183
- value: { name: 'kid3', age: 3 }
184
- }
185
- ]
186
- },
187
- {
188
- type: 'remove',
189
- key: 'age',
190
- value: 55
191
- }
174
+ // Sample diffs array, similar to the one generated in the diff example
175
+ const diffs = [
176
+ // ... Diff objects here
192
177
  ];
193
178
 
194
- changesets.applyChanges(oldObj, diffs);
195
- expect(oldObj).to.eql({
196
- name: 'smith',
197
- coins: [2, 5, 1],
198
- children: [
199
- { name: 'kid3', age: 3 },
200
- { name: 'kid1', age: 0 },
201
- { name: 'kid2', age: 2 }
202
- ]
179
+ changesets.applyChanges(oldData, diffs);
180
+
181
+ expect(oldData).to.eql({
182
+ // ... Updated data here
203
183
  });
204
184
  ```
205
185
 
206
- ### revertChange
186
+ ### `revertChange`
207
187
 
208
188
  #### Examples:
209
189
 
210
190
  ```javascript
191
+ const newData = {
192
+ // ... Updated data here
193
+ };
211
194
 
212
- var changesets = require('json-diff-ts');
213
-
214
- var newObj = {
215
- name: 'smith',
216
- coins: [2, 5, 1],
217
- children: [
218
- {name: 'kid3', age: 3},
219
- {name: 'kid1', age: 0},
220
- {name: 'kid2', age: 2}
221
- ]};
222
-
223
- // Assume children is an array of child object and the child object has 'name' as its primary key
224
- diffs = [
225
- {
226
- type: 'update', key: 'name', value: 'smith', oldValue: 'joe'
227
- },
228
- {
229
- type: 'update', key: 'coins', embededKey: '$index', changes: [
230
- {type: 'add', key: '2', value: 1 }
231
- ]
232
- },
233
- {
234
- type: 'update',
235
- key: 'children',
236
- embededKey: 'name', // The key property name of the elements in an array
237
- changes: [
238
- {
239
- type: 'update', key: 'kid1', changes: [
240
- {type: 'update', key: 'age', value: 0, oldValue: 1 }
241
- ]
242
- },
243
- {
244
- type: 'add', key: 'kid3', value: {name: 'kid3', age: 3 }
245
- }
246
- ]
247
- },
248
- {
249
- type: 'remove', key: 'age', value: 55
250
- }
251
- ]
252
-
253
- changesets.revertChanges(newObj, diffs)
254
- expect(newObj).to.eql {
255
- name: 'joe',
256
- age: 55,
257
- coins: [2, 5],
258
- children: [
259
- {name: 'kid1', age: 1},
260
- {name: 'kid2', age: 2}
261
- ]};
262
-
263
- ```
195
+ // Sample diffs array
196
+ const diffs = [
197
+ // ... Diff objects here
198
+ ];
264
199
 
265
- ## Get started
200
+ changesets.revertChanges(newData, diffs);
266
201
 
267
- ```
268
- npm install json-diff-ts
202
+ expect(newData).to.eql({
203
+ // ... Original data restored here
204
+ });
269
205
  ```
270
206
 
271
- ## Run the test
207
+ ### `jsonPath`
272
208
 
273
- ```
274
- npm run test
275
- ```
209
+ The `json-diff-ts` library uses JSONPath to address specific parts of a JSON document in both the changeset and the application/reversion of changes.
276
210
 
277
- ## Contact
211
+ #### Examples:
278
212
 
279
- Blog: https://blog.leitwolf.io
213
+ ```javascript
280
214
 
281
- Twitter: [@cglessner](https://twitter.com/cglessner)
215
+ const jsonPath = changesets.jsonPath;
282
216
 
283
- ## Changelog
284
- - v1.2.6 Improve handling of JSON Path segments that include periods (PR by [EdVinyard](https://github.com/EdVinyard))
285
- - v1.2.5 Patch all dependencies; add support for resolving a key name if a function is used to get the key value
286
- - v1.2.4 Fix readme (npm install); update TypeScript and Lodash
287
- - v1.2.3 Update outdated dependencies; update TypeScript version to 4.5.2
288
- - v1.2.2 Add support for functions to resove object keys (PR by [Abraxxa](https://github.com/abraxxa))
217
+ cost data = {
218
+ // ... Some JSON data
219
+ };
289
220
 
290
- ## Credits
221
+ const value = jsonPath.query(data, '$.characters[?(@.id=="LUK")].name');
291
222
 
292
- This project was based on https://www.npmjs.com/package/diff-json (viruschidai@gmail.com)
223
+ expect(value).to.eql(['Luke Skywalker']);
224
+ ```
293
225
 
294
- ## Licence
226
+ ## Contributing
295
227
 
296
- **The MIT License (MIT)**
228
+ Contributions are welcome! Please follow the provided issue templates and code of conduct.
297
229
 
298
- Copyright (c) 2019 Christian Glessner
230
+ ## Contact
299
231
 
300
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
232
+ Reach out to the maintainer via LinkedIn or Twitter:
301
233
 
302
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
234
+ - LinkedIn: [Christian Glessner](https://www.linkedin.com/in/christian-glessner/)
235
+ - Twitter: [@leitwolf_io](https://twitter.com/leitwolf_io)
303
236
 
304
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
237
+ Discover more about the company behind this project: [hololux](https://hololux.com)
305
238
 
306
- The project is based on diff-json (https://www.npmjs.com/package/diff-json). Copyright 2013 viruschidai@gmail.com. for additional details.
239
+ ## Release Notes
307
240
 
308
- **Original License**
241
+ - **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
+ - **v1.2.6:** Enhanced JSON Path handling for period-inclusive segments.
243
+ - **v1.2.5:** Patched dependencies; added key name resolution support for key functions.
244
+ - **v1.2.4:** Documentation updates; upgraded TypeScript and Lodash.
245
+ - **v1.2.3:** Dependency updates; switched to TypeScript 4.5.2.
246
+ - **v1.2.2:** Implemented object key resolution functions support.
309
247
 
310
- The MIT License (MIT)
248
+ ## Acknowledgments
311
249
 
312
- Copyright (c) 2013 viruschidai@gmail.com
250
+ This project takes inspiration and code from [diff-json](https://www.npmjs.com/package/diff-json) by viruschidai@gmail.com.
313
251
 
314
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
252
+ ## License
315
253
 
316
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
254
+ json-diff-ts is open-sourced software licensed under the [MIT license](LICENSE).
317
255
 
318
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
256
+ The original diff-json project is also under the MIT License. For more information, refer to its [license details](https://www.npmjs.com/package/diff-json#license).
package/lib/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './jsonDiff';
2
- export * from './jsonCompare';
1
+ export * from './jsonDiff.js';
2
+ export * from './jsonCompare.js';
package/lib/index.js CHANGED
@@ -1,18 +1,2 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./jsonDiff"), exports);
18
- __exportStar(require("./jsonCompare"), exports);
1
+ export * from './jsonDiff.js';
2
+ export * from './jsonCompare.js';
@@ -1,4 +1,4 @@
1
- import { IFlatChange, Operation } from './jsonDiff';
1
+ import { IFlatChange, Operation } from './jsonDiff.js';
2
2
  export declare enum CompareOperation {
3
3
  CONTAINER = "CONTAINER",
4
4
  UNCHANGED = "UNCHANGED"
@@ -1,64 +1,61 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compare = exports.applyChangelist = exports.enrich = exports.createContainer = exports.createValue = exports.CompareOperation = void 0;
4
- const lodash_1 = require("lodash");
5
- const jsonDiff_1 = require("./jsonDiff");
6
- var CompareOperation;
1
+ import { chain, keys, replace, set } from 'lodash-es';
2
+ import { diff, flattenChangeset, getTypeOfObj, Operation } from './jsonDiff.js';
3
+ export var CompareOperation;
7
4
  (function (CompareOperation) {
8
5
  CompareOperation["CONTAINER"] = "CONTAINER";
9
6
  CompareOperation["UNCHANGED"] = "UNCHANGED";
10
- })(CompareOperation = exports.CompareOperation || (exports.CompareOperation = {}));
11
- const createValue = (value) => ({ type: CompareOperation.UNCHANGED, value });
12
- exports.createValue = createValue;
13
- const createContainer = (value) => ({
7
+ })(CompareOperation || (CompareOperation = {}));
8
+ export const createValue = (value) => ({ type: CompareOperation.UNCHANGED, value });
9
+ export const createContainer = (value) => ({
14
10
  type: CompareOperation.CONTAINER,
15
11
  value
16
12
  });
17
- exports.createContainer = createContainer;
18
- const enrich = (object) => {
19
- const objectType = (0, jsonDiff_1.getTypeOfObj)(object);
13
+ export const enrich = (object) => {
14
+ const objectType = getTypeOfObj(object);
20
15
  switch (objectType) {
21
16
  case 'Object':
22
- return (0, lodash_1.keys)(object)
23
- .map((key) => ({ key, value: (0, exports.enrich)(object[key]) }))
17
+ return keys(object)
18
+ .map((key) => ({ key, value: enrich(object[key]) }))
24
19
  .reduce((accumulator, entry) => {
25
20
  accumulator.value[entry.key] = entry.value;
26
21
  return accumulator;
27
- }, (0, exports.createContainer)({}));
22
+ }, createContainer({}));
28
23
  case 'Array':
29
- return (0, lodash_1.chain)(object)
30
- .map(value => (0, exports.enrich)(value))
24
+ return chain(object)
25
+ .map((value) => enrich(value))
31
26
  .reduce((accumulator, value) => {
32
27
  accumulator.value.push(value);
33
28
  return accumulator;
34
- }, (0, exports.createContainer)([]))
29
+ }, createContainer([]))
35
30
  .value();
36
31
  case 'Function':
37
32
  return undefined;
38
33
  case 'Date':
39
34
  default:
40
35
  // Primitive value
41
- return (0, exports.createValue)(object);
36
+ return createValue(object);
42
37
  }
43
38
  };
44
- exports.enrich = enrich;
45
- const applyChangelist = (object, changelist) => {
46
- (0, lodash_1.chain)(changelist)
47
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, '$.', '.') })))
48
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, /(\[(?<array>\d)\]\.)/g, 'ARRVAL_START$<array>ARRVAL_END') })))
49
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, /(?<dot>\.)/g, '.value$<dot>') })))
50
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, /\./, '') })))
51
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, /ARRVAL_START/g, '.value[') })))
52
- .map(entry => (Object.assign(Object.assign({}, entry), { path: (0, lodash_1.replace)(entry.path, /ARRVAL_END/g, '].value.') })))
39
+ export const applyChangelist = (object, changelist) => {
40
+ chain(changelist)
41
+ .map((entry) => ({ ...entry, path: replace(entry.path, '$.', '.') }))
42
+ .map((entry) => ({
43
+ ...entry,
44
+ path: replace(entry.path, /(\[(?<array>\d)\]\.)/g, 'ARRVAL_START$<array>ARRVAL_END')
45
+ }))
46
+ .map((entry) => ({ ...entry, path: replace(entry.path, /(?<dot>\.)/g, '.value$<dot>') }))
47
+ .map((entry) => ({ ...entry, path: replace(entry.path, /\./, '') }))
48
+ .map((entry) => ({ ...entry, path: replace(entry.path, /ARRVAL_START/g, '.value[') }))
49
+ .map((entry) => ({ ...entry, path: replace(entry.path, /ARRVAL_END/g, '].value.') }))
53
50
  .value()
54
- .forEach(entry => {
51
+ .forEach((entry) => {
55
52
  switch (entry.type) {
56
- case jsonDiff_1.Operation.ADD:
57
- case jsonDiff_1.Operation.UPDATE:
58
- (0, lodash_1.set)(object, entry.path, { type: entry.type, value: entry.value, oldValue: entry.oldValue });
53
+ case Operation.ADD:
54
+ case Operation.UPDATE:
55
+ set(object, entry.path, { type: entry.type, value: entry.value, oldValue: entry.oldValue });
59
56
  break;
60
- case jsonDiff_1.Operation.REMOVE:
61
- (0, lodash_1.set)(object, entry.path, { type: entry.type, value: undefined, oldValue: entry.value });
57
+ case Operation.REMOVE:
58
+ set(object, entry.path, { type: entry.type, value: undefined, oldValue: entry.value });
62
59
  break;
63
60
  default:
64
61
  throw new Error();
@@ -66,8 +63,6 @@ const applyChangelist = (object, changelist) => {
66
63
  });
67
64
  return object;
68
65
  };
69
- exports.applyChangelist = applyChangelist;
70
- const compare = (oldObject, newObject) => {
71
- return (0, exports.applyChangelist)((0, exports.enrich)(oldObject), (0, jsonDiff_1.flattenChangeset)((0, jsonDiff_1.diff)(oldObject, newObject)));
66
+ export const compare = (oldObject, newObject) => {
67
+ return applyChangelist(enrich(oldObject), flattenChangeset(diff(oldObject, newObject)));
72
68
  };
73
- exports.compare = compare;
package/lib/jsonDiff.d.ts CHANGED
@@ -1,9 +1,6 @@
1
- import { Dictionary } from 'lodash';
2
- declare type FunctionKey = (obj: any, getKeyName?: boolean) => any;
3
- export declare const getTypeOfObj: (obj: any) => string;
4
- export declare const diff: (oldObj: any, newObj: any, embeddedObjKeys?: Dictionary<string | FunctionKey>) => IChange[];
5
- export declare const applyChangeset: (obj: any, changeset: Changeset) => any;
6
- export declare const revertChangeset: (obj: any, changeset: Changeset) => any;
1
+ type FunctionKey = (obj: any, shouldReturnKeyName?: boolean) => any;
2
+ export type EmbeddedObjKeysType = Record<string, string | FunctionKey>;
3
+ export type EmbeddedObjKeysMapType = Map<string | RegExp, string | FunctionKey>;
7
4
  export declare enum Operation {
8
5
  REMOVE = "REMOVE",
9
6
  ADD = "ADD",
@@ -17,7 +14,7 @@ export interface IChange {
17
14
  oldValue?: any;
18
15
  changes?: IChange[];
19
16
  }
20
- export declare type Changeset = IChange[];
17
+ export type Changeset = IChange[];
21
18
  export interface IFlatChange {
22
19
  type: Operation;
23
20
  key: string;
@@ -26,6 +23,68 @@ export interface IFlatChange {
26
23
  value?: any;
27
24
  oldValue?: any;
28
25
  }
26
+ export declare function diff(oldObj: any, newObj: any, embeddedObjKeys?: EmbeddedObjKeysType | EmbeddedObjKeysMapType): IChange[];
27
+ /**
28
+ * Applies all changes in the changeset to the object.
29
+ *
30
+ * @param {any} obj - The object to apply changes to.
31
+ * @param {Changeset} changeset - The changeset to apply.
32
+ * @returns {any} - The object after the changes from the changeset have been applied.
33
+ *
34
+ * The function first checks if a changeset is provided. If so, it iterates over each change in the changeset.
35
+ * If the change value is not null or undefined, or if the change type is REMOVE, it applies the change to the object directly.
36
+ * Otherwise, it applies the change to the corresponding branch of the object.
37
+ */
38
+ export declare const applyChangeset: (obj: any, changeset: Changeset) => any;
39
+ /**
40
+ * Reverts the changes made to an object based on a given changeset.
41
+ *
42
+ * @param {any} obj - The object on which to revert changes.
43
+ * @param {Changeset} changeset - The changeset to revert.
44
+ * @returns {any} - The object after the changes from the changeset have been reverted.
45
+ *
46
+ * The function first checks if a changeset is provided. If so, it reverses the changeset to start reverting from the last change.
47
+ * It then iterates over each change in the changeset. If the change does not have any nested changes, it reverts the change on the object directly.
48
+ * If the change does have nested changes, it reverts the changes on the corresponding branch of the object.
49
+ */
50
+ export declare const revertChangeset: (obj: any, changeset: Changeset) => any;
51
+ /**
52
+ * Flattens a changeset into an array of flat changes.
53
+ *
54
+ * @param {Changeset | IChange} obj - The changeset or change to flatten.
55
+ * @param {string} [path='$'] - The current path in the changeset.
56
+ * @param {string | FunctionKey} [embeddedKey] - The key to use for embedded objects.
57
+ * @returns {IFlatChange[]} - An array of flat changes.
58
+ *
59
+ * The function first checks if the input is an array. If so, it recursively flattens each change in the array.
60
+ * If the input is not an array, it checks if the change has nested changes or an embedded key.
61
+ * If so, it updates the path and recursively flattens the nested changes or the embedded object.
62
+ * If the change does not have nested changes or an embedded key, it creates a flat change and returns it in an array.
63
+ */
29
64
  export declare const flattenChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey) => IFlatChange[];
65
+ /**
66
+ * Transforms a flat changeset into a nested changeset.
67
+ *
68
+ * @param {IFlatChange | IFlatChange[]} changes - The flat changeset to unflatten.
69
+ * @returns {IChange[]} - The unflattened changeset.
70
+ *
71
+ * The function first checks if the input is a single change or an array of changes.
72
+ * It then iterates over each change and splits its path into segments.
73
+ * For each segment, it checks if it represents an array or a leaf node.
74
+ * If it represents an array, it creates a new change object and updates the pointer to this new object.
75
+ * If it represents a leaf node, it sets the key, type, value, and oldValue of the current change object.
76
+ * Finally, it pushes the unflattened change object into the changes array.
77
+ */
30
78
  export declare const unflattenChanges: (changes: IFlatChange | IFlatChange[]) => IChange[];
79
+ /**
80
+ * Determines the type of a given object.
81
+ *
82
+ * @param {any} obj - The object whose type is to be determined.
83
+ * @returns {string | null} - The type of the object, or null if the object is null.
84
+ *
85
+ * This function first checks if the object is undefined or null, and returns 'undefined' or null respectively.
86
+ * If the object is neither undefined nor null, it uses Object.prototype.toString to get the object's type.
87
+ * The type is extracted from the string returned by Object.prototype.toString using a regular expression.
88
+ */
89
+ export declare const getTypeOfObj: (obj: any) => string;
31
90
  export {};
package/lib/jsonDiff.js CHANGED
@@ -1,25 +1,250 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unflattenChanges = exports.flattenChangeset = exports.Operation = exports.revertChangeset = exports.applyChangeset = exports.diff = exports.getTypeOfObj = void 0;
4
- const lodash_1 = require("lodash");
5
- const getTypeOfObj = (obj) => {
1
+ import { difference, find, intersection, keyBy } from 'lodash-es';
2
+ export var Operation;
3
+ (function (Operation) {
4
+ Operation["REMOVE"] = "REMOVE";
5
+ Operation["ADD"] = "ADD";
6
+ Operation["UPDATE"] = "UPDATE";
7
+ })(Operation || (Operation = {}));
8
+ export function diff(oldObj, newObj, embeddedObjKeys) {
9
+ return compare(oldObj, newObj, [], embeddedObjKeys, []);
10
+ }
11
+ /**
12
+ * Applies all changes in the changeset to the object.
13
+ *
14
+ * @param {any} obj - The object to apply changes to.
15
+ * @param {Changeset} changeset - The changeset to apply.
16
+ * @returns {any} - The object after the changes from the changeset have been applied.
17
+ *
18
+ * The function first checks if a changeset is provided. If so, it iterates over each change in the changeset.
19
+ * If the change value is not null or undefined, or if the change type is REMOVE, it applies the change to the object directly.
20
+ * Otherwise, it applies the change to the corresponding branch of the object.
21
+ */
22
+ export const applyChangeset = (obj, changeset) => {
23
+ if (changeset) {
24
+ changeset.forEach((change) => {
25
+ const { type, key, value, embeddedKey } = change;
26
+ if ((value !== null && value !== undefined) || type === Operation.REMOVE) {
27
+ // Apply the change to the object
28
+ applyLeafChange(obj, change, embeddedKey);
29
+ }
30
+ else {
31
+ // Apply the change to the branch
32
+ applyBranchChange(obj[key], change);
33
+ }
34
+ });
35
+ }
36
+ return obj;
37
+ };
38
+ /**
39
+ * Reverts the changes made to an object based on a given changeset.
40
+ *
41
+ * @param {any} obj - The object on which to revert changes.
42
+ * @param {Changeset} changeset - The changeset to revert.
43
+ * @returns {any} - The object after the changes from the changeset have been reverted.
44
+ *
45
+ * The function first checks if a changeset is provided. If so, it reverses the changeset to start reverting from the last change.
46
+ * It then iterates over each change in the changeset. If the change does not have any nested changes, it reverts the change on the object directly.
47
+ * If the change does have nested changes, it reverts the changes on the corresponding branch of the object.
48
+ */
49
+ export const revertChangeset = (obj, changeset) => {
50
+ if (changeset) {
51
+ changeset
52
+ .reverse()
53
+ .forEach((change) => !change.changes ? revertLeafChange(obj, change) : revertBranchChange(obj[change.key], change));
54
+ }
55
+ return obj;
56
+ };
57
+ /**
58
+ * Flattens a changeset into an array of flat changes.
59
+ *
60
+ * @param {Changeset | IChange} obj - The changeset or change to flatten.
61
+ * @param {string} [path='$'] - The current path in the changeset.
62
+ * @param {string | FunctionKey} [embeddedKey] - The key to use for embedded objects.
63
+ * @returns {IFlatChange[]} - An array of flat changes.
64
+ *
65
+ * The function first checks if the input is an array. If so, it recursively flattens each change in the array.
66
+ * If the input is not an array, it checks if the change has nested changes or an embedded key.
67
+ * If so, it updates the path and recursively flattens the nested changes or the embedded object.
68
+ * If the change does not have nested changes or an embedded key, it creates a flat change and returns it in an array.
69
+ */
70
+ export const flattenChangeset = (obj, path = '$', embeddedKey) => {
71
+ if (Array.isArray(obj)) {
72
+ return obj.reduce((memo, change) => [...memo, ...flattenChangeset(change, path, embeddedKey)], []);
73
+ }
74
+ else {
75
+ if (obj.changes || embeddedKey) {
76
+ path = embeddedKey
77
+ ? embeddedKey === '$index'
78
+ ? `${path}[${obj.key}]`
79
+ : obj.type === Operation.ADD
80
+ ? path
81
+ : filterExpression(path, embeddedKey, obj.key)
82
+ : (path = append(path, obj.key));
83
+ return flattenChangeset(obj.changes || obj, path, obj.embeddedKey);
84
+ }
85
+ else {
86
+ const valueType = getTypeOfObj(obj.value);
87
+ return [
88
+ {
89
+ ...obj,
90
+ path: valueType === 'Object' || path.endsWith(`[${obj.key}]`) ? path : append(path, obj.key),
91
+ valueType
92
+ }
93
+ ];
94
+ }
95
+ }
96
+ };
97
+ /**
98
+ * Transforms a flat changeset into a nested changeset.
99
+ *
100
+ * @param {IFlatChange | IFlatChange[]} changes - The flat changeset to unflatten.
101
+ * @returns {IChange[]} - The unflattened changeset.
102
+ *
103
+ * The function first checks if the input is a single change or an array of changes.
104
+ * It then iterates over each change and splits its path into segments.
105
+ * For each segment, it checks if it represents an array or a leaf node.
106
+ * If it represents an array, it creates a new change object and updates the pointer to this new object.
107
+ * If it represents a leaf node, it sets the key, type, value, and oldValue of the current change object.
108
+ * Finally, it pushes the unflattened change object into the changes array.
109
+ */
110
+ export const unflattenChanges = (changes) => {
111
+ if (!Array.isArray(changes)) {
112
+ changes = [changes];
113
+ }
114
+ const changesArr = [];
115
+ changes.forEach((change) => {
116
+ const obj = {};
117
+ let ptr = obj;
118
+ const segments = change.path.split(/([^@])\./).reduce((acc, curr, i) => {
119
+ const x = Math.floor(i / 2);
120
+ if (!acc[x]) {
121
+ acc[x] = '';
122
+ }
123
+ acc[x] += curr;
124
+ return acc;
125
+ }, []);
126
+ if (segments.length === 1) {
127
+ ptr.key = change.key;
128
+ ptr.type = change.type;
129
+ ptr.value = change.value;
130
+ ptr.oldValue = change.oldValue;
131
+ changesArr.push(ptr);
132
+ }
133
+ else {
134
+ for (let i = 1; i < segments.length; i++) {
135
+ const segment = segments[i];
136
+ // Matches JSONPath segments: "items[?(@.id='123')]" or "items[2]"
137
+ const result = /^(.+)\[\?\(@\.(.+)='(.+)'\)]$|^(.+)\[(\d+)\]/.exec(segment);
138
+ // array
139
+ if (result) {
140
+ let key;
141
+ let embeddedKey;
142
+ let arrKey;
143
+ if (result[1]) {
144
+ key = result[1];
145
+ embeddedKey = result[2];
146
+ arrKey = result[3];
147
+ }
148
+ else {
149
+ key = result[4];
150
+ embeddedKey = '$index';
151
+ arrKey = Number(result[5]);
152
+ }
153
+ // leaf
154
+ if (i === segments.length - 1) {
155
+ ptr.key = key;
156
+ ptr.embeddedKey = embeddedKey;
157
+ ptr.type = Operation.UPDATE;
158
+ ptr.changes = [
159
+ {
160
+ type: change.type,
161
+ key: arrKey,
162
+ value: change.value,
163
+ oldValue: change.oldValue
164
+ }
165
+ ];
166
+ }
167
+ else {
168
+ // object
169
+ ptr.key = key;
170
+ ptr.embeddedKey = embeddedKey;
171
+ ptr.type = Operation.UPDATE;
172
+ const newPtr = {};
173
+ ptr.changes = [
174
+ {
175
+ type: Operation.UPDATE,
176
+ key: arrKey,
177
+ changes: [newPtr]
178
+ }
179
+ ];
180
+ ptr = newPtr;
181
+ }
182
+ }
183
+ else {
184
+ // leaf
185
+ if (i === segments.length - 1) {
186
+ // check if value is a primitive or object
187
+ if (change.value !== null && change.valueType === 'Object') {
188
+ ptr.key = segment;
189
+ ptr.type = Operation.UPDATE;
190
+ ptr.changes = [
191
+ {
192
+ key: change.key,
193
+ type: change.type,
194
+ value: change.value
195
+ }
196
+ ];
197
+ }
198
+ else {
199
+ ptr.key = change.key;
200
+ ptr.type = change.type;
201
+ ptr.value = change.value;
202
+ ptr.oldValue = change.oldValue;
203
+ }
204
+ }
205
+ else {
206
+ // branch
207
+ ptr.key = segment;
208
+ ptr.type = Operation.UPDATE;
209
+ const newPtr = {};
210
+ ptr.changes = [newPtr];
211
+ ptr = newPtr;
212
+ }
213
+ }
214
+ }
215
+ changesArr.push(obj);
216
+ }
217
+ });
218
+ return changesArr;
219
+ };
220
+ /**
221
+ * Determines the type of a given object.
222
+ *
223
+ * @param {any} obj - The object whose type is to be determined.
224
+ * @returns {string | null} - The type of the object, or null if the object is null.
225
+ *
226
+ * This function first checks if the object is undefined or null, and returns 'undefined' or null respectively.
227
+ * If the object is neither undefined nor null, it uses Object.prototype.toString to get the object's type.
228
+ * The type is extracted from the string returned by Object.prototype.toString using a regular expression.
229
+ */
230
+ export const getTypeOfObj = (obj) => {
6
231
  if (typeof obj === 'undefined') {
7
232
  return 'undefined';
8
233
  }
9
234
  if (obj === null) {
10
235
  return null;
11
236
  }
237
+ // Extracts the "Type" from "[object Type]" string.
12
238
  return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
13
239
  };
14
- exports.getTypeOfObj = getTypeOfObj;
15
240
  const getKey = (path) => {
16
241
  const left = path[path.length - 1];
17
242
  return left != null ? left : '$root';
18
243
  };
19
244
  const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
20
245
  let changes = [];
21
- const typeOfOldObj = (0, exports.getTypeOfObj)(oldObj);
22
- const typeOfNewObj = (0, exports.getTypeOfObj)(newObj);
246
+ const typeOfOldObj = getTypeOfObj(oldObj);
247
+ const typeOfNewObj = getTypeOfObj(newObj);
23
248
  // if type of object changes, consider it as old obj has been deleted and a new object has been added
24
249
  if (typeOfOldObj !== typeOfNewObj) {
25
250
  changes.push({ type: Operation.REMOVE, key: getKey(path), value: oldObj });
@@ -28,7 +253,11 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
28
253
  }
29
254
  switch (typeOfOldObj) {
30
255
  case 'Date':
31
- changes = changes.concat(comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => (Object.assign(Object.assign({}, x), { value: new Date(x.value), oldValue: new Date(x.oldValue) }))));
256
+ changes = changes.concat(comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({
257
+ ...x,
258
+ value: new Date(x.value),
259
+ oldValue: new Date(x.oldValue)
260
+ })));
32
261
  break;
33
262
  case 'Object':
34
263
  const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath);
@@ -66,7 +295,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
66
295
  let changes = [];
67
296
  const oldObjKeys = Object.keys(oldObj);
68
297
  const newObjKeys = Object.keys(newObj);
69
- const intersectionKeys = (0, lodash_1.intersection)(oldObjKeys, newObjKeys);
298
+ const intersectionKeys = intersection(oldObjKeys, newObjKeys);
70
299
  for (k of intersectionKeys) {
71
300
  newPath = path.concat([k]);
72
301
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -75,7 +304,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
75
304
  changes = changes.concat(diffs);
76
305
  }
77
306
  }
78
- const addedKeys = (0, lodash_1.difference)(newObjKeys, oldObjKeys);
307
+ const addedKeys = difference(newObjKeys, oldObjKeys);
79
308
  for (k of addedKeys) {
80
309
  newPath = path.concat([k]);
81
310
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -85,7 +314,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
85
314
  value: newObj[k]
86
315
  });
87
316
  }
88
- const deletedKeys = (0, lodash_1.difference)(oldObjKeys, newObjKeys);
317
+ const deletedKeys = difference(oldObjKeys, newObjKeys);
89
318
  for (k of deletedKeys) {
90
319
  newPath = path.concat([k]);
91
320
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -120,22 +349,29 @@ const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
120
349
  const getObjectKey = (embeddedObjKeys, keyPath) => {
121
350
  if (embeddedObjKeys != null) {
122
351
  const path = keyPath.join('.');
352
+ if (embeddedObjKeys instanceof Map) {
353
+ for (const [key, value] of embeddedObjKeys.entries()) {
354
+ if (key instanceof RegExp) {
355
+ if (path.match(key)) {
356
+ return value;
357
+ }
358
+ }
359
+ else if (path === key) {
360
+ return value;
361
+ }
362
+ }
363
+ }
123
364
  const key = embeddedObjKeys[path];
124
365
  if (key != null) {
125
366
  return key;
126
367
  }
127
- for (const regex in embeddedObjKeys) {
128
- if (path.match(new RegExp(regex))) {
129
- return embeddedObjKeys[regex];
130
- }
131
- }
132
368
  }
133
369
  return undefined;
134
370
  };
135
371
  const convertArrayToObj = (arr, uniqKey) => {
136
372
  let obj = {};
137
373
  if (uniqKey !== '$index') {
138
- obj = (0, lodash_1.keyBy)(arr, uniqKey);
374
+ obj = keyBy(arr, uniqKey);
139
375
  }
140
376
  else {
141
377
  for (let i = 0; i < arr.length; i++) {
@@ -157,7 +393,6 @@ const comparePrimitives = (oldObj, newObj, path) => {
157
393
  }
158
394
  return changes;
159
395
  };
160
- // const isEmbeddedKey = key => /\$.*=/gi.test(key)
161
396
  const removeKey = (obj, key, embeddedKey) => {
162
397
  if (Array.isArray(obj)) {
163
398
  if (embeddedKey === '$index') {
@@ -219,9 +454,9 @@ const applyArrayChange = (arr, change) => (() => {
219
454
  element = arr[subchange.key];
220
455
  }
221
456
  else {
222
- element = (0, lodash_1.find)(arr, (el) => el[change.embeddedKey].toString() === subchange.key.toString());
457
+ element = find(arr, (el) => el[change.embeddedKey].toString() === subchange.key.toString());
223
458
  }
224
- result.push((0, exports.applyChangeset)(element, subchange.changes));
459
+ result.push(applyChangeset(element, subchange.changes));
225
460
  }
226
461
  }
227
462
  return result;
@@ -231,7 +466,7 @@ const applyBranchChange = (obj, change) => {
231
466
  return applyArrayChange(obj, change);
232
467
  }
233
468
  else {
234
- return (0, exports.applyChangeset)(obj, change.changes);
469
+ return applyChangeset(obj, change.changes);
235
470
  }
236
471
  };
237
472
  const revertLeafChange = (obj, change, embeddedKey = '$index') => {
@@ -257,9 +492,9 @@ const revertArrayChange = (arr, change) => (() => {
257
492
  element = arr[+subchange.key];
258
493
  }
259
494
  else {
260
- element = (0, lodash_1.find)(arr, (el) => el[change.embeddedKey].toString() === subchange.key);
495
+ element = find(arr, (el) => el[change.embeddedKey].toString() === subchange.key);
261
496
  }
262
- result.push((0, exports.revertChangeset)(element, subchange.changes));
497
+ result.push(revertChangeset(element, subchange.changes));
263
498
  }
264
499
  }
265
500
  return result;
@@ -269,182 +504,12 @@ const revertBranchChange = (obj, change) => {
269
504
  return revertArrayChange(obj, change);
270
505
  }
271
506
  else {
272
- return (0, exports.revertChangeset)(obj, change.changes);
273
- }
274
- };
275
- const diff = (oldObj, newObj, embeddedObjKeys) => compare(oldObj, newObj, [], embeddedObjKeys, []);
276
- exports.diff = diff;
277
- const applyChangeset = (obj, changeset) => {
278
- if (changeset) {
279
- changeset.forEach((change) => (change.value !== null && change.value !== undefined) || change.type === Operation.REMOVE
280
- ? applyLeafChange(obj, change, change.embeddedKey)
281
- : applyBranchChange(obj[change.key], change));
282
- }
283
- return obj;
284
- };
285
- exports.applyChangeset = applyChangeset;
286
- const revertChangeset = (obj, changeset) => {
287
- if (changeset) {
288
- changeset
289
- .reverse()
290
- .forEach((change) => !change.changes ? revertLeafChange(obj, change) : revertBranchChange(obj[change.key], change));
291
- }
292
- return obj;
293
- };
294
- exports.revertChangeset = revertChangeset;
295
- var Operation;
296
- (function (Operation) {
297
- Operation["REMOVE"] = "REMOVE";
298
- Operation["ADD"] = "ADD";
299
- Operation["UPDATE"] = "UPDATE";
300
- })(Operation = exports.Operation || (exports.Operation = {}));
301
- const flattenChangeset = (obj, path = '$', embeddedKey) => {
302
- if (Array.isArray(obj)) {
303
- return obj.reduce((memo, change) => [...memo, ...(0, exports.flattenChangeset)(change, path, embeddedKey)], []);
304
- }
305
- else {
306
- if (obj.changes || embeddedKey) {
307
- path = embeddedKey
308
- ? embeddedKey === '$index'
309
- ? `${path}[${obj.key}]`
310
- : obj.type === Operation.ADD
311
- ? path
312
- : filterExpression(path, embeddedKey, obj.key)
313
- : (path = append(path, obj.key));
314
- return (0, exports.flattenChangeset)(obj.changes || obj, path, obj.embeddedKey);
315
- }
316
- else {
317
- const valueType = (0, exports.getTypeOfObj)(obj.value);
318
- return [
319
- Object.assign(Object.assign({}, obj), { path: valueType === 'Object' || path.endsWith(`[${obj.key}]`)
320
- ? path
321
- : append(path, obj.key), valueType })
322
- ];
323
- }
324
- }
325
- };
326
- exports.flattenChangeset = flattenChangeset;
327
- const unflattenChanges = (changes) => {
328
- if (!Array.isArray(changes)) {
329
- changes = [changes];
507
+ return revertChangeset(obj, change.changes);
330
508
  }
331
- const changesArr = [];
332
- changes.forEach((change) => {
333
- const obj = {};
334
- let ptr = obj;
335
- const segments = change.path.split(/([^@])\./).reduce((acc, curr, i) => {
336
- const x = Math.floor(i / 2);
337
- if (!acc[x]) {
338
- acc[x] = '';
339
- }
340
- acc[x] += curr;
341
- return acc;
342
- }, []);
343
- // $.childern[@.name='chris'].age
344
- // =>
345
- // $
346
- // childern[@.name='chris']
347
- // age
348
- if (segments.length === 1) {
349
- ptr.key = change.key;
350
- ptr.type = change.type;
351
- ptr.value = change.value;
352
- ptr.oldValue = change.oldValue;
353
- changesArr.push(ptr);
354
- }
355
- else {
356
- for (let i = 1; i < segments.length; i++) {
357
- const segment = segments[i];
358
- // check for array
359
- const result = /^(.+)\[\?\(@\.(.+)='(.+)'\)]$|^(.+)\[(\d+)\]/.exec(segment);
360
- // array
361
- if (result) {
362
- let key;
363
- let embeddedKey;
364
- let arrKey;
365
- if (result[1]) {
366
- key = result[1];
367
- embeddedKey = result[2];
368
- arrKey = result[3];
369
- }
370
- else {
371
- key = result[4];
372
- embeddedKey = '$index';
373
- arrKey = Number(result[5]);
374
- }
375
- // leaf
376
- if (i === segments.length - 1) {
377
- ptr.key = key;
378
- ptr.embeddedKey = embeddedKey;
379
- ptr.type = Operation.UPDATE;
380
- ptr.changes = [
381
- {
382
- type: change.type,
383
- key: arrKey,
384
- value: change.value,
385
- oldValue: change.oldValue
386
- }
387
- ];
388
- }
389
- else {
390
- // object
391
- ptr.key = key;
392
- ptr.embeddedKey = embeddedKey;
393
- ptr.type = Operation.UPDATE;
394
- const newPtr = {};
395
- ptr.changes = [
396
- {
397
- type: Operation.UPDATE,
398
- key: arrKey,
399
- changes: [newPtr]
400
- }
401
- ];
402
- ptr = newPtr;
403
- }
404
- }
405
- else {
406
- // leaf
407
- if (i === segments.length - 1) {
408
- // check if value is a primitive or object
409
- if (change.value !== null && change.valueType === 'Object') {
410
- ptr.key = segment;
411
- ptr.type = Operation.UPDATE;
412
- ptr.changes = [
413
- {
414
- key: change.key,
415
- type: change.type,
416
- value: change.value
417
- }
418
- ];
419
- }
420
- else {
421
- ptr.key = change.key;
422
- ptr.type = change.type;
423
- ptr.value = change.value;
424
- ptr.oldValue = change.oldValue;
425
- }
426
- }
427
- else {
428
- // branch
429
- ptr.key = segment;
430
- ptr.type = Operation.UPDATE;
431
- const newPtr = {};
432
- ptr.changes = [newPtr];
433
- ptr = newPtr;
434
- }
435
- }
436
- }
437
- changesArr.push(obj);
438
- }
439
- });
440
- return changesArr;
441
509
  };
442
- exports.unflattenChanges = unflattenChanges;
443
510
  /** combine a base JSON Path with a subsequent segment */
444
511
  function append(basePath, nextSegment) {
445
- return nextSegment.includes('.')
446
- ? `${basePath}[${nextSegment}]`
447
- : `${basePath}.${nextSegment}`;
512
+ return nextSegment.includes('.') ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
448
513
  }
449
514
  /** returns a JSON Path filter expression; e.g., `$.pet[(?name='spot')]` */
450
515
  function filterExpression(basePath, filterKey, filterValue) {
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "json-diff-ts",
3
- "version": "1.2.6",
4
- "description": "A diff tool for JavaScript based on https://www.npmjs.com/package/diff-json written in TypeScript.",
3
+ "version": "2.0.0",
4
+ "description": "A JSON diff tool for JavaScript written in TypeScript.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
+ "type": "module",
7
8
  "scripts": {
8
9
  "build": "tsc",
9
- "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
10
- "lint": "tslint -p tsconfig.json",
11
- "test": "jest --config jest.config.json",
12
- "test:watch": "jest --watch --config jest.config.json",
10
+ "format": "prettier --write \"src/**/*.ts\"",
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",
13
14
  "prepare": "npm run build",
14
15
  "prepublishOnly": "npm test && npm run lint",
15
16
  "preversion": "npm run lint",
@@ -37,16 +38,14 @@
37
38
  },
38
39
  "homepage": "https://github.com/ltwlf/json-diff-ts#readme",
39
40
  "devDependencies": {
40
- "@types/jest": "^29.1.1",
41
- "@types/lodash": "^4.14.149",
42
- "jest": "^24.9.0",
43
- "prettier": "^2.5.1",
44
- "ts-jest": "^24.2.0",
45
- "tslint": "^6.1.3",
46
- "tslint-config-prettier": "^1.18.0",
47
- "typescript": "^4.5.2"
48
- },
49
- "peerDependencies": {
50
- "lodash": "^4.x"
41
+ "@jest/globals": "^29.7.0",
42
+ "@types/jest": "^29.5.7",
43
+ "@types/lodash-es": "^4.17.10",
44
+ "eslint": "^8.53.0",
45
+ "jest": "^29.5.0",
46
+ "lodash-es": "^4.17.21",
47
+ "prettier": "^3.0.3",
48
+ "ts-jest": "^29.0.5",
49
+ "typescript": "^4.9.5"
51
50
  }
52
- }
51
+ }