json-diff-ts 1.2.6 → 2.1.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/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,76 @@ export interface IFlatChange {
26
23
  value?: any;
27
24
  oldValue?: any;
28
25
  }
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): IChange[];
35
+ /**
36
+ * Applies all changes in the changeset to the object.
37
+ *
38
+ * @param {any} obj - The object to apply changes to.
39
+ * @param {Changeset} changeset - The changeset to apply.
40
+ * @returns {any} - The object after the changes from the changeset have been applied.
41
+ *
42
+ * The function first checks if a changeset is provided. If so, it iterates over each change in the changeset.
43
+ * If the change value is not null or undefined, or if the change type is REMOVE, it applies the change to the object directly.
44
+ * Otherwise, it applies the change to the corresponding branch of the object.
45
+ */
46
+ export declare const applyChangeset: (obj: any, changeset: Changeset) => any;
47
+ /**
48
+ * Reverts the changes made to an object based on a given changeset.
49
+ *
50
+ * @param {any} obj - The object on which to revert changes.
51
+ * @param {Changeset} changeset - The changeset to revert.
52
+ * @returns {any} - The object after the changes from the changeset have been reverted.
53
+ *
54
+ * The function first checks if a changeset is provided. If so, it reverses the changeset to start reverting from the last change.
55
+ * 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.
56
+ * If the change does have nested changes, it reverts the changes on the corresponding branch of the object.
57
+ */
58
+ export declare const revertChangeset: (obj: any, changeset: Changeset) => any;
59
+ /**
60
+ * Flattens a changeset into an array of flat changes.
61
+ *
62
+ * @param {Changeset | IChange} obj - The changeset or change to flatten.
63
+ * @param {string} [path='$'] - The current path in the changeset.
64
+ * @param {string | FunctionKey} [embeddedKey] - The key to use for embedded objects.
65
+ * @returns {IFlatChange[]} - An array of flat changes.
66
+ *
67
+ * The function first checks if the input is an array. If so, it recursively flattens each change in the array.
68
+ * If the input is not an array, it checks if the change has nested changes or an embedded key.
69
+ * If so, it updates the path and recursively flattens the nested changes or the embedded object.
70
+ * If the change does not have nested changes or an embedded key, it creates a flat change and returns it in an array.
71
+ */
29
72
  export declare const flattenChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey) => IFlatChange[];
73
+ /**
74
+ * Transforms a flat changeset into a nested changeset.
75
+ *
76
+ * @param {IFlatChange | IFlatChange[]} changes - The flat changeset to unflatten.
77
+ * @returns {IChange[]} - The unflattened changeset.
78
+ *
79
+ * The function first checks if the input is a single change or an array of changes.
80
+ * It then iterates over each change and splits its path into segments.
81
+ * For each segment, it checks if it represents an array or a leaf node.
82
+ * If it represents an array, it creates a new change object and updates the pointer to this new object.
83
+ * If it represents a leaf node, it sets the key, type, value, and oldValue of the current change object.
84
+ * Finally, it pushes the unflattened change object into the changes array.
85
+ */
30
86
  export declare const unflattenChanges: (changes: IFlatChange | IFlatChange[]) => IChange[];
87
+ /**
88
+ * Determines the type of a given object.
89
+ *
90
+ * @param {any} obj - The object whose type is to be determined.
91
+ * @returns {string | null} - The type of the object, or null if the object is null.
92
+ *
93
+ * This function first checks if the object is undefined or null, and returns 'undefined' or null respectively.
94
+ * If the object is neither undefined nor null, it uses Object.prototype.toString to get the object's type.
95
+ * The type is extracted from the string returned by Object.prototype.toString using a regular expression.
96
+ */
97
+ export declare const getTypeOfObj: (obj: any) => string;
31
98
  export {};
package/lib/jsonDiff.js CHANGED
@@ -1,25 +1,262 @@
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
+ /**
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) {
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, []);
29
+ }
30
+ /**
31
+ * Applies all changes in the changeset to the object.
32
+ *
33
+ * @param {any} obj - The object to apply changes to.
34
+ * @param {Changeset} changeset - The changeset to apply.
35
+ * @returns {any} - The object after the changes from the changeset have been applied.
36
+ *
37
+ * The function first checks if a changeset is provided. If so, it iterates over each change in the changeset.
38
+ * If the change value is not null or undefined, or if the change type is REMOVE, it applies the change to the object directly.
39
+ * Otherwise, it applies the change to the corresponding branch of the object.
40
+ */
41
+ export const applyChangeset = (obj, changeset) => {
42
+ if (changeset) {
43
+ changeset.forEach((change) => {
44
+ const { type, key, value, embeddedKey } = change;
45
+ if ((value !== null && value !== undefined) || type === Operation.REMOVE) {
46
+ // Apply the change to the object
47
+ applyLeafChange(obj, change, embeddedKey);
48
+ }
49
+ else {
50
+ // Apply the change to the branch
51
+ applyBranchChange(obj[key], change);
52
+ }
53
+ });
54
+ }
55
+ return obj;
56
+ };
57
+ /**
58
+ * Reverts the changes made to an object based on a given changeset.
59
+ *
60
+ * @param {any} obj - The object on which to revert changes.
61
+ * @param {Changeset} changeset - The changeset to revert.
62
+ * @returns {any} - The object after the changes from the changeset have been reverted.
63
+ *
64
+ * The function first checks if a changeset is provided. If so, it reverses the changeset to start reverting from the last change.
65
+ * 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.
66
+ * If the change does have nested changes, it reverts the changes on the corresponding branch of the object.
67
+ */
68
+ export const revertChangeset = (obj, changeset) => {
69
+ if (changeset) {
70
+ changeset
71
+ .reverse()
72
+ .forEach((change) => !change.changes ? revertLeafChange(obj, change) : revertBranchChange(obj[change.key], change));
73
+ }
74
+ return obj;
75
+ };
76
+ /**
77
+ * Flattens a changeset into an array of flat changes.
78
+ *
79
+ * @param {Changeset | IChange} obj - The changeset or change to flatten.
80
+ * @param {string} [path='$'] - The current path in the changeset.
81
+ * @param {string | FunctionKey} [embeddedKey] - The key to use for embedded objects.
82
+ * @returns {IFlatChange[]} - An array of flat changes.
83
+ *
84
+ * The function first checks if the input is an array. If so, it recursively flattens each change in the array.
85
+ * If the input is not an array, it checks if the change has nested changes or an embedded key.
86
+ * If so, it updates the path and recursively flattens the nested changes or the embedded object.
87
+ * If the change does not have nested changes or an embedded key, it creates a flat change and returns it in an array.
88
+ */
89
+ export const flattenChangeset = (obj, path = '$', embeddedKey) => {
90
+ if (Array.isArray(obj)) {
91
+ return obj.reduce((memo, change) => [...memo, ...flattenChangeset(change, path, embeddedKey)], []);
92
+ }
93
+ else {
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));
102
+ return flattenChangeset(obj.changes || obj, path, obj.embeddedKey);
103
+ }
104
+ else {
105
+ const valueType = getTypeOfObj(obj.value);
106
+ return [
107
+ {
108
+ ...obj,
109
+ path: valueType === 'Object' || path.endsWith(`[${obj.key}]`) ? path : append(path, obj.key),
110
+ valueType
111
+ }
112
+ ];
113
+ }
114
+ }
115
+ };
116
+ /**
117
+ * Transforms a flat changeset into a nested changeset.
118
+ *
119
+ * @param {IFlatChange | IFlatChange[]} changes - The flat changeset to unflatten.
120
+ * @returns {IChange[]} - The unflattened changeset.
121
+ *
122
+ * The function first checks if the input is a single change or an array of changes.
123
+ * It then iterates over each change and splits its path into segments.
124
+ * For each segment, it checks if it represents an array or a leaf node.
125
+ * If it represents an array, it creates a new change object and updates the pointer to this new object.
126
+ * If it represents a leaf node, it sets the key, type, value, and oldValue of the current change object.
127
+ * Finally, it pushes the unflattened change object into the changes array.
128
+ */
129
+ export const unflattenChanges = (changes) => {
130
+ if (!Array.isArray(changes)) {
131
+ changes = [changes];
132
+ }
133
+ const changesArr = [];
134
+ changes.forEach((change) => {
135
+ const obj = {};
136
+ let ptr = obj;
137
+ const segments = change.path.split(/(?<=[^@])\.(?=[^@])/);
138
+ if (segments.length === 1) {
139
+ ptr.key = change.key;
140
+ ptr.type = change.type;
141
+ ptr.value = change.value;
142
+ ptr.oldValue = change.oldValue;
143
+ changesArr.push(ptr);
144
+ }
145
+ else {
146
+ for (let i = 1; i < segments.length; i++) {
147
+ 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);
150
+ // array
151
+ if (result) {
152
+ let key;
153
+ let embeddedKey;
154
+ let arrKey;
155
+ if (result[1]) {
156
+ key = result[1];
157
+ embeddedKey = result[2];
158
+ arrKey = result[3];
159
+ }
160
+ else {
161
+ key = result[5];
162
+ embeddedKey = '$index';
163
+ arrKey = Number(result[6]);
164
+ }
165
+ // leaf
166
+ if (i === segments.length - 1) {
167
+ ptr.key = key;
168
+ ptr.embeddedKey = embeddedKey;
169
+ ptr.type = Operation.UPDATE;
170
+ ptr.changes = [
171
+ {
172
+ type: change.type,
173
+ key: arrKey,
174
+ value: change.value,
175
+ oldValue: change.oldValue
176
+ }
177
+ ];
178
+ }
179
+ else {
180
+ // object
181
+ ptr.key = key;
182
+ ptr.embeddedKey = embeddedKey;
183
+ ptr.type = Operation.UPDATE;
184
+ const newPtr = {};
185
+ ptr.changes = [
186
+ {
187
+ type: Operation.UPDATE,
188
+ key: arrKey,
189
+ changes: [newPtr]
190
+ }
191
+ ];
192
+ ptr = newPtr;
193
+ }
194
+ }
195
+ else {
196
+ // leaf
197
+ if (i === segments.length - 1) {
198
+ // check if value is a primitive or object
199
+ if (change.value !== null && change.valueType === 'Object') {
200
+ ptr.key = segment;
201
+ ptr.type = Operation.UPDATE;
202
+ ptr.changes = [
203
+ {
204
+ key: change.key,
205
+ type: change.type,
206
+ value: change.value
207
+ }
208
+ ];
209
+ }
210
+ else {
211
+ ptr.key = change.key;
212
+ ptr.type = change.type;
213
+ ptr.value = change.value;
214
+ ptr.oldValue = change.oldValue;
215
+ }
216
+ }
217
+ else {
218
+ // branch
219
+ ptr.key = segment;
220
+ ptr.type = Operation.UPDATE;
221
+ const newPtr = {};
222
+ ptr.changes = [newPtr];
223
+ ptr = newPtr;
224
+ }
225
+ }
226
+ }
227
+ changesArr.push(obj);
228
+ }
229
+ });
230
+ return changesArr;
231
+ };
232
+ /**
233
+ * Determines the type of a given object.
234
+ *
235
+ * @param {any} obj - The object whose type is to be determined.
236
+ * @returns {string | null} - The type of the object, or null if the object is null.
237
+ *
238
+ * This function first checks if the object is undefined or null, and returns 'undefined' or null respectively.
239
+ * If the object is neither undefined nor null, it uses Object.prototype.toString to get the object's type.
240
+ * The type is extracted from the string returned by Object.prototype.toString using a regular expression.
241
+ */
242
+ export const getTypeOfObj = (obj) => {
6
243
  if (typeof obj === 'undefined') {
7
244
  return 'undefined';
8
245
  }
9
246
  if (obj === null) {
10
247
  return null;
11
248
  }
249
+ // Extracts the "Type" from "[object Type]" string.
12
250
  return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
13
251
  };
14
- exports.getTypeOfObj = getTypeOfObj;
15
252
  const getKey = (path) => {
16
253
  const left = path[path.length - 1];
17
254
  return left != null ? left : '$root';
18
255
  };
19
256
  const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
20
257
  let changes = [];
21
- const typeOfOldObj = (0, exports.getTypeOfObj)(oldObj);
22
- const typeOfNewObj = (0, exports.getTypeOfObj)(newObj);
258
+ const typeOfOldObj = getTypeOfObj(oldObj);
259
+ const typeOfNewObj = getTypeOfObj(newObj);
23
260
  // if type of object changes, consider it as old obj has been deleted and a new object has been added
24
261
  if (typeOfOldObj !== typeOfNewObj) {
25
262
  changes.push({ type: Operation.REMOVE, key: getKey(path), value: oldObj });
@@ -28,7 +265,11 @@ const compare = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
28
265
  }
29
266
  switch (typeOfOldObj) {
30
267
  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) }))));
268
+ changes = changes.concat(comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({
269
+ ...x,
270
+ value: new Date(x.value),
271
+ oldValue: new Date(x.oldValue)
272
+ })));
32
273
  break;
33
274
  case 'Object':
34
275
  const diffs = compareObject(oldObj, newObj, path, embeddedObjKeys, keyPath);
@@ -66,7 +307,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
66
307
  let changes = [];
67
308
  const oldObjKeys = Object.keys(oldObj);
68
309
  const newObjKeys = Object.keys(newObj);
69
- const intersectionKeys = (0, lodash_1.intersection)(oldObjKeys, newObjKeys);
310
+ const intersectionKeys = intersection(oldObjKeys, newObjKeys);
70
311
  for (k of intersectionKeys) {
71
312
  newPath = path.concat([k]);
72
313
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -75,7 +316,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
75
316
  changes = changes.concat(diffs);
76
317
  }
77
318
  }
78
- const addedKeys = (0, lodash_1.difference)(newObjKeys, oldObjKeys);
319
+ const addedKeys = difference(newObjKeys, oldObjKeys);
79
320
  for (k of addedKeys) {
80
321
  newPath = path.concat([k]);
81
322
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -85,7 +326,7 @@ const compareObject = (oldObj, newObj, path, embeddedObjKeys, keyPath, skipPath
85
326
  value: newObj[k]
86
327
  });
87
328
  }
88
- const deletedKeys = (0, lodash_1.difference)(oldObjKeys, newObjKeys);
329
+ const deletedKeys = difference(oldObjKeys, newObjKeys);
89
330
  for (k of deletedKeys) {
90
331
  newPath = path.concat([k]);
91
332
  newKeyPath = skipPath ? keyPath : keyPath.concat([k]);
@@ -120,22 +361,29 @@ const compareArray = (oldObj, newObj, path, embeddedObjKeys, keyPath) => {
120
361
  const getObjectKey = (embeddedObjKeys, keyPath) => {
121
362
  if (embeddedObjKeys != null) {
122
363
  const path = keyPath.join('.');
364
+ if (embeddedObjKeys instanceof Map) {
365
+ for (const [key, value] of embeddedObjKeys.entries()) {
366
+ if (key instanceof RegExp) {
367
+ if (path.match(key)) {
368
+ return value;
369
+ }
370
+ }
371
+ else if (path === key) {
372
+ return value;
373
+ }
374
+ }
375
+ }
123
376
  const key = embeddedObjKeys[path];
124
377
  if (key != null) {
125
378
  return key;
126
379
  }
127
- for (const regex in embeddedObjKeys) {
128
- if (path.match(new RegExp(regex))) {
129
- return embeddedObjKeys[regex];
130
- }
131
- }
132
380
  }
133
381
  return undefined;
134
382
  };
135
383
  const convertArrayToObj = (arr, uniqKey) => {
136
384
  let obj = {};
137
385
  if (uniqKey !== '$index') {
138
- obj = (0, lodash_1.keyBy)(arr, uniqKey);
386
+ obj = keyBy(arr, uniqKey);
139
387
  }
140
388
  else {
141
389
  for (let i = 0; i < arr.length; i++) {
@@ -157,7 +405,6 @@ const comparePrimitives = (oldObj, newObj, path) => {
157
405
  }
158
406
  return changes;
159
407
  };
160
- // const isEmbeddedKey = key => /\$.*=/gi.test(key)
161
408
  const removeKey = (obj, key, embeddedKey) => {
162
409
  if (Array.isArray(obj)) {
163
410
  if (embeddedKey === '$index') {
@@ -219,9 +466,9 @@ const applyArrayChange = (arr, change) => (() => {
219
466
  element = arr[subchange.key];
220
467
  }
221
468
  else {
222
- element = (0, lodash_1.find)(arr, (el) => el[change.embeddedKey].toString() === subchange.key.toString());
469
+ element = find(arr, (el) => el[change.embeddedKey]?.toString() === subchange.key.toString());
223
470
  }
224
- result.push((0, exports.applyChangeset)(element, subchange.changes));
471
+ result.push(applyChangeset(element, subchange.changes));
225
472
  }
226
473
  }
227
474
  return result;
@@ -231,7 +478,7 @@ const applyBranchChange = (obj, change) => {
231
478
  return applyArrayChange(obj, change);
232
479
  }
233
480
  else {
234
- return (0, exports.applyChangeset)(obj, change.changes);
481
+ return applyChangeset(obj, change.changes);
235
482
  }
236
483
  };
237
484
  const revertLeafChange = (obj, change, embeddedKey = '$index') => {
@@ -257,9 +504,9 @@ const revertArrayChange = (arr, change) => (() => {
257
504
  element = arr[+subchange.key];
258
505
  }
259
506
  else {
260
- element = (0, lodash_1.find)(arr, (el) => el[change.embeddedKey].toString() === subchange.key);
507
+ element = find(arr, (el) => el[change.embeddedKey].toString() === subchange.key);
261
508
  }
262
- result.push((0, exports.revertChangeset)(element, subchange.changes));
509
+ result.push(revertChangeset(element, subchange.changes));
263
510
  }
264
511
  }
265
512
  return result;
@@ -269,186 +516,17 @@ const revertBranchChange = (obj, change) => {
269
516
  return revertArrayChange(obj, change);
270
517
  }
271
518
  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));
519
+ return revertChangeset(obj, change.changes);
291
520
  }
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];
330
- }
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
521
  };
442
- exports.unflattenChanges = unflattenChanges;
443
522
  /** combine a base JSON Path with a subsequent segment */
444
523
  function append(basePath, nextSegment) {
445
- return nextSegment.includes('.')
446
- ? `${basePath}[${nextSegment}]`
447
- : `${basePath}.${nextSegment}`;
524
+ return nextSegment.includes('.') ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
448
525
  }
449
526
  /** returns a JSON Path filter expression; e.g., `$.pet[(?name='spot')]` */
450
527
  function filterExpression(basePath, filterKey, filterValue) {
528
+ const value = typeof filterValue === 'number' ? filterValue : `'${filterValue}'`;
451
529
  return typeof filterKey === 'string' && filterKey.includes('.')
452
- ? `${basePath}[?(@[${filterKey}]='${filterValue}')]`
453
- : `${basePath}[?(@.${filterKey}='${filterValue}')]`;
530
+ ? `${basePath}[?(@[${filterKey}]==${value})]`
531
+ : `${basePath}[?(@.${filterKey}==${value})]`;
454
532
  }