json-diff-ts 5.0.0-alpha.4 → 5.0.0-alpha.6
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/dist/index.cjs +68 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +68 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -11,6 +11,8 @@ interface IChange {
|
|
|
11
11
|
type: Operation;
|
|
12
12
|
key: string;
|
|
13
13
|
embeddedKey?: string | FunctionKey;
|
|
14
|
+
/** When true, embeddedKey is a dot-separated nested path (e.g. "a.b" → @.a.b). */
|
|
15
|
+
embeddedKeyIsPath?: boolean;
|
|
14
16
|
value?: any;
|
|
15
17
|
oldValue?: any;
|
|
16
18
|
changes?: IChange[];
|
|
@@ -80,7 +82,7 @@ declare const revertChangeset: (obj: any, changeset: Changeset) => any;
|
|
|
80
82
|
* If so, it updates the path and recursively flattens the nested changes or the embedded object.
|
|
81
83
|
* If the change does not have nested changes or an embedded key, it creates a atomic change and returns it in an array.
|
|
82
84
|
*/
|
|
83
|
-
declare const atomizeChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey) => IAtomicChange[];
|
|
85
|
+
declare const atomizeChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey, embeddedKeyIsPath?: boolean) => IAtomicChange[];
|
|
84
86
|
/**
|
|
85
87
|
* Transforms an atomized changeset into a nested changeset.
|
|
86
88
|
*
|
|
@@ -163,6 +165,7 @@ type AtomPathSegment = {
|
|
|
163
165
|
type: 'keyFilter';
|
|
164
166
|
property: string;
|
|
165
167
|
value: unknown;
|
|
168
|
+
literalKey?: boolean;
|
|
166
169
|
} | {
|
|
167
170
|
type: 'valueFilter';
|
|
168
171
|
value: unknown;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ interface IChange {
|
|
|
11
11
|
type: Operation;
|
|
12
12
|
key: string;
|
|
13
13
|
embeddedKey?: string | FunctionKey;
|
|
14
|
+
/** When true, embeddedKey is a dot-separated nested path (e.g. "a.b" → @.a.b). */
|
|
15
|
+
embeddedKeyIsPath?: boolean;
|
|
14
16
|
value?: any;
|
|
15
17
|
oldValue?: any;
|
|
16
18
|
changes?: IChange[];
|
|
@@ -80,7 +82,7 @@ declare const revertChangeset: (obj: any, changeset: Changeset) => any;
|
|
|
80
82
|
* If so, it updates the path and recursively flattens the nested changes or the embedded object.
|
|
81
83
|
* If the change does not have nested changes or an embedded key, it creates a atomic change and returns it in an array.
|
|
82
84
|
*/
|
|
83
|
-
declare const atomizeChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey) => IAtomicChange[];
|
|
85
|
+
declare const atomizeChangeset: (obj: Changeset | IChange, path?: string, embeddedKey?: string | FunctionKey, embeddedKeyIsPath?: boolean) => IAtomicChange[];
|
|
84
86
|
/**
|
|
85
87
|
* Transforms an atomized changeset into a nested changeset.
|
|
86
88
|
*
|
|
@@ -163,6 +165,7 @@ type AtomPathSegment = {
|
|
|
163
165
|
type: 'keyFilter';
|
|
164
166
|
property: string;
|
|
165
167
|
value: unknown;
|
|
168
|
+
literalKey?: boolean;
|
|
166
169
|
} | {
|
|
167
170
|
type: 'valueFilter';
|
|
168
171
|
value: unknown;
|
package/dist/index.js
CHANGED
|
@@ -107,12 +107,12 @@ var revertChangeset = (obj, changeset) => {
|
|
|
107
107
|
}
|
|
108
108
|
return obj;
|
|
109
109
|
};
|
|
110
|
-
var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
110
|
+
var atomizeChangeset = (obj, path = "$", embeddedKey, embeddedKeyIsPath) => {
|
|
111
111
|
if (Array.isArray(obj)) {
|
|
112
|
-
return handleArray(obj, path, embeddedKey);
|
|
112
|
+
return handleArray(obj, path, embeddedKey, embeddedKeyIsPath);
|
|
113
113
|
} else if (obj.changes || embeddedKey) {
|
|
114
114
|
if (embeddedKey) {
|
|
115
|
-
const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path);
|
|
115
|
+
const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path, embeddedKeyIsPath);
|
|
116
116
|
path = updatedPath;
|
|
117
117
|
if (atomicChange) {
|
|
118
118
|
return atomicChange;
|
|
@@ -120,7 +120,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
120
120
|
} else {
|
|
121
121
|
path = append(path, obj.key);
|
|
122
122
|
}
|
|
123
|
-
return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey);
|
|
123
|
+
return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey, obj.embeddedKeyIsPath);
|
|
124
124
|
} else {
|
|
125
125
|
const valueType = getTypeOfObj(obj.value);
|
|
126
126
|
let finalPath = path;
|
|
@@ -147,7 +147,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
147
147
|
];
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
|
-
function handleEmbeddedKey(embeddedKey, obj, path) {
|
|
150
|
+
function handleEmbeddedKey(embeddedKey, obj, path, isPath) {
|
|
151
151
|
if (embeddedKey === "$index") {
|
|
152
152
|
path = `${path}[${obj.key}]`;
|
|
153
153
|
return [path];
|
|
@@ -165,12 +165,12 @@ function handleEmbeddedKey(embeddedKey, obj, path) {
|
|
|
165
165
|
]
|
|
166
166
|
];
|
|
167
167
|
} else {
|
|
168
|
-
path = filterExpression(path, embeddedKey, obj.key);
|
|
168
|
+
path = filterExpression(path, embeddedKey, obj.key, isPath);
|
|
169
169
|
return [path];
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
-
var handleArray = (obj, path, embeddedKey) => {
|
|
173
|
-
return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey)], []);
|
|
172
|
+
var handleArray = (obj, path, embeddedKey, embeddedKeyIsPath) => {
|
|
173
|
+
return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey, embeddedKeyIsPath)], []);
|
|
174
174
|
};
|
|
175
175
|
var unatomizeChangeset = (changes) => {
|
|
176
176
|
if (!Array.isArray(changes)) {
|
|
@@ -190,15 +190,17 @@ var unatomizeChangeset = (changes) => {
|
|
|
190
190
|
} else {
|
|
191
191
|
for (let i = 1; i < segments.length; i++) {
|
|
192
192
|
const segment = segments[i];
|
|
193
|
-
const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*)'\]))=+'([^']
|
|
193
|
+
const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*(?:''[^']*)*)'\]))=+'([^']*(?:''[^']*)*)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
|
|
194
194
|
if (result) {
|
|
195
195
|
let key;
|
|
196
196
|
let embeddedKey;
|
|
197
197
|
let arrKey;
|
|
198
|
+
let isPath;
|
|
198
199
|
if (result[1]) {
|
|
199
200
|
key = result[1];
|
|
200
|
-
embeddedKey = result[3] || result[2] || "$value";
|
|
201
|
-
|
|
201
|
+
embeddedKey = result[3]?.replace(/''/g, "'") || result[2] || "$value";
|
|
202
|
+
isPath = !result[3] && !!result[2] && result[2].includes(".") && NESTED_PATH_RE.test(result[2]) ? true : void 0;
|
|
203
|
+
arrKey = result[4]?.replace(/''/g, "'");
|
|
202
204
|
} else {
|
|
203
205
|
key = result[5];
|
|
204
206
|
embeddedKey = "$index";
|
|
@@ -207,6 +209,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
207
209
|
if (i === segments.length - 1) {
|
|
208
210
|
ptr.key = key;
|
|
209
211
|
ptr.embeddedKey = embeddedKey;
|
|
212
|
+
if (isPath) ptr.embeddedKeyIsPath = true;
|
|
210
213
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
211
214
|
ptr.changes = [
|
|
212
215
|
{
|
|
@@ -219,6 +222,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
219
222
|
} else {
|
|
220
223
|
ptr.key = key;
|
|
221
224
|
ptr.embeddedKey = embeddedKey;
|
|
225
|
+
if (isPath) ptr.embeddedKeyIsPath = true;
|
|
222
226
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
223
227
|
const newPtr = {};
|
|
224
228
|
ptr.changes = [
|
|
@@ -420,12 +424,15 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
|
|
|
420
424
|
const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
|
|
421
425
|
const indexedNewObj = convertArrayToObj(newObj, uniqKey);
|
|
422
426
|
const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options);
|
|
427
|
+
const isFunctionKey = typeof uniqKey === "function" && uniqKey.length === 2;
|
|
423
428
|
if (diffs.length) {
|
|
429
|
+
const resolvedKey = isFunctionKey ? uniqKey(newObj[0] ?? oldObj[0], true) : uniqKey;
|
|
424
430
|
return [
|
|
425
431
|
{
|
|
426
432
|
type: "UPDATE" /* UPDATE */,
|
|
427
433
|
key: getKey(path),
|
|
428
|
-
embeddedKey:
|
|
434
|
+
embeddedKey: resolvedKey,
|
|
435
|
+
...isFunctionKey && typeof resolvedKey === "string" && NESTED_PATH_RE.test(resolvedKey) && resolvedKey.includes(".") ? { embeddedKeyIsPath: true } : {},
|
|
429
436
|
changes: diffs
|
|
430
437
|
}
|
|
431
438
|
];
|
|
@@ -483,13 +490,13 @@ var comparePrimitives = (oldObj, newObj, path) => {
|
|
|
483
490
|
}
|
|
484
491
|
return changes;
|
|
485
492
|
};
|
|
486
|
-
var removeKey = (obj, key, embeddedKey) => {
|
|
493
|
+
var removeKey = (obj, key, embeddedKey, isPath) => {
|
|
487
494
|
if (Array.isArray(obj)) {
|
|
488
495
|
if (embeddedKey === "$index") {
|
|
489
496
|
obj.splice(Number(key), 1);
|
|
490
497
|
return;
|
|
491
498
|
}
|
|
492
|
-
const index = indexOfItemInArray(obj, embeddedKey, key);
|
|
499
|
+
const index = indexOfItemInArray(obj, embeddedKey, key, isPath);
|
|
493
500
|
if (index === -1) {
|
|
494
501
|
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array!`);
|
|
495
502
|
return;
|
|
@@ -500,13 +507,20 @@ var removeKey = (obj, key, embeddedKey) => {
|
|
|
500
507
|
return;
|
|
501
508
|
}
|
|
502
509
|
};
|
|
503
|
-
var
|
|
510
|
+
var resolveProperty = (obj, key, isPath) => {
|
|
511
|
+
if (obj == null) return void 0;
|
|
512
|
+
if (typeof key !== "string" || !isPath || !key.includes(".")) return obj[key];
|
|
513
|
+
return key.split(".").reduce((cur, seg) => cur?.[seg], obj);
|
|
514
|
+
};
|
|
515
|
+
var indexOfItemInArray = (arr, key, value, isPath) => {
|
|
504
516
|
if (key === "$value") {
|
|
505
517
|
return arr.indexOf(value);
|
|
506
518
|
}
|
|
507
519
|
for (let i = 0; i < arr.length; i++) {
|
|
508
520
|
const item = arr[i];
|
|
509
|
-
if (item
|
|
521
|
+
if (item == null) continue;
|
|
522
|
+
const resolved = resolveProperty(item, key, isPath);
|
|
523
|
+
if (resolved != null && String(resolved) === String(value)) {
|
|
510
524
|
return i;
|
|
511
525
|
}
|
|
512
526
|
}
|
|
@@ -524,7 +538,7 @@ var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
|
524
538
|
return obj ? obj[key] = value : null;
|
|
525
539
|
}
|
|
526
540
|
};
|
|
527
|
-
var applyLeafChange = (obj, change, embeddedKey) => {
|
|
541
|
+
var applyLeafChange = (obj, change, embeddedKey, isPath) => {
|
|
528
542
|
const { type, key, value } = change;
|
|
529
543
|
switch (type) {
|
|
530
544
|
case "ADD" /* ADD */:
|
|
@@ -532,7 +546,7 @@ var applyLeafChange = (obj, change, embeddedKey) => {
|
|
|
532
546
|
case "UPDATE" /* UPDATE */:
|
|
533
547
|
return modifyKeyValue(obj, key, value);
|
|
534
548
|
case "REMOVE" /* REMOVE */:
|
|
535
|
-
return removeKey(obj, key, embeddedKey);
|
|
549
|
+
return removeKey(obj, key, embeddedKey, isPath);
|
|
536
550
|
}
|
|
537
551
|
};
|
|
538
552
|
var applyArrayChange = (arr, change) => {
|
|
@@ -549,7 +563,7 @@ var applyArrayChange = (arr, change) => {
|
|
|
549
563
|
}
|
|
550
564
|
for (const subchange of changes) {
|
|
551
565
|
if (subchange.value !== null && subchange.value !== void 0 || subchange.type === "REMOVE" /* REMOVE */ || subchange.value === null && subchange.type === "ADD" /* ADD */ || subchange.value === void 0 && subchange.type === "ADD" /* ADD */) {
|
|
552
|
-
applyLeafChange(arr, subchange, change.embeddedKey);
|
|
566
|
+
applyLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
|
|
553
567
|
} else {
|
|
554
568
|
let element;
|
|
555
569
|
if (change.embeddedKey === "$index") {
|
|
@@ -560,7 +574,10 @@ var applyArrayChange = (arr, change) => {
|
|
|
560
574
|
element = arr[index];
|
|
561
575
|
}
|
|
562
576
|
} else {
|
|
563
|
-
element = arr.find((el) =>
|
|
577
|
+
element = arr.find((el) => {
|
|
578
|
+
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
579
|
+
return resolved != null && String(resolved) === String(subchange.key);
|
|
580
|
+
});
|
|
564
581
|
}
|
|
565
582
|
if (element) {
|
|
566
583
|
applyChangeset(element, subchange.changes);
|
|
@@ -576,7 +593,7 @@ var applyBranchChange = (obj, change) => {
|
|
|
576
593
|
return applyChangeset(obj, change.changes);
|
|
577
594
|
}
|
|
578
595
|
};
|
|
579
|
-
var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
596
|
+
var revertLeafChange = (obj, change, embeddedKey = "$index", isPath) => {
|
|
580
597
|
const { type, key, value, oldValue } = change;
|
|
581
598
|
if (key === "$root") {
|
|
582
599
|
switch (type) {
|
|
@@ -606,7 +623,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
606
623
|
}
|
|
607
624
|
switch (type) {
|
|
608
625
|
case "ADD" /* ADD */:
|
|
609
|
-
return removeKey(obj, key, embeddedKey);
|
|
626
|
+
return removeKey(obj, key, embeddedKey, isPath);
|
|
610
627
|
case "UPDATE" /* UPDATE */:
|
|
611
628
|
return modifyKeyValue(obj, key, oldValue);
|
|
612
629
|
case "REMOVE" /* REMOVE */:
|
|
@@ -616,7 +633,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
616
633
|
var revertArrayChange = (arr, change) => {
|
|
617
634
|
for (const subchange of change.changes) {
|
|
618
635
|
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) {
|
|
619
|
-
revertLeafChange(arr, subchange, change.embeddedKey);
|
|
636
|
+
revertLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
|
|
620
637
|
} else {
|
|
621
638
|
let element;
|
|
622
639
|
if (change.embeddedKey === "$index") {
|
|
@@ -627,7 +644,10 @@ var revertArrayChange = (arr, change) => {
|
|
|
627
644
|
element = arr[index];
|
|
628
645
|
}
|
|
629
646
|
} else {
|
|
630
|
-
element = arr.find((el) =>
|
|
647
|
+
element = arr.find((el) => {
|
|
648
|
+
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
649
|
+
return resolved != null && String(resolved) === String(subchange.key);
|
|
650
|
+
});
|
|
631
651
|
}
|
|
632
652
|
if (element) {
|
|
633
653
|
revertChangeset(element, subchange.changes);
|
|
@@ -647,10 +667,18 @@ function append(basePath, nextSegment) {
|
|
|
647
667
|
return nextSegment.includes(".") ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
|
|
648
668
|
}
|
|
649
669
|
var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
|
|
670
|
+
var NESTED_PATH_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
671
|
+
function filterExpression(basePath, filterKey, filterValue, isPath) {
|
|
672
|
+
const escapedValue = `'${filterValue.replace(/'/g, "''")}'`;
|
|
673
|
+
let memberAccess;
|
|
674
|
+
if (isPath && NESTED_PATH_RE.test(filterKey)) {
|
|
675
|
+
memberAccess = "." + filterKey;
|
|
676
|
+
} else if (IDENT_RE.test(filterKey)) {
|
|
677
|
+
memberAccess = `.${filterKey}`;
|
|
678
|
+
} else {
|
|
679
|
+
memberAccess = `['${filterKey.replace(/'/g, "''")}']`;
|
|
680
|
+
}
|
|
681
|
+
return `${basePath}[?(@${memberAccess}==${escapedValue})]`;
|
|
654
682
|
}
|
|
655
683
|
|
|
656
684
|
// src/jsonCompare.ts
|
|
@@ -867,12 +895,13 @@ function findFilterClose(s, from) {
|
|
|
867
895
|
}
|
|
868
896
|
return -1;
|
|
869
897
|
}
|
|
898
|
+
var NESTED_PATH_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
870
899
|
function parseFilter(inner) {
|
|
871
900
|
if (inner.startsWith("@.")) {
|
|
872
901
|
const eq = inner.indexOf("==");
|
|
873
902
|
if (eq === -1) throw new Error(`Invalid filter: missing '==' in ${inner}`);
|
|
874
903
|
const key = inner.slice(2, eq);
|
|
875
|
-
if (!key || !
|
|
904
|
+
if (!key || !NESTED_PATH_RE2.test(key)) {
|
|
876
905
|
throw new Error(`Invalid property name in filter: '${key}'. Use bracket notation for non-identifier keys: @['${key}']`);
|
|
877
906
|
}
|
|
878
907
|
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(eq + 2)) };
|
|
@@ -880,7 +909,7 @@ function parseFilter(inner) {
|
|
|
880
909
|
if (inner.startsWith("@['")) {
|
|
881
910
|
const [key, endIdx] = extractQuotedString(inner, 3);
|
|
882
911
|
const valStart = endIdx + 4;
|
|
883
|
-
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)) };
|
|
912
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)), literalKey: true };
|
|
884
913
|
}
|
|
885
914
|
if (inner.startsWith("@==")) {
|
|
886
915
|
return { type: "valueFilter", value: parseFilterLiteral(inner.slice(3)) };
|
|
@@ -954,7 +983,14 @@ function buildAtomPath(segments) {
|
|
|
954
983
|
result += `[${seg.index}]`;
|
|
955
984
|
break;
|
|
956
985
|
case "keyFilter": {
|
|
957
|
-
|
|
986
|
+
let memberAccess;
|
|
987
|
+
if (seg.literalKey) {
|
|
988
|
+
memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
|
|
989
|
+
} else if (NESTED_PATH_RE2.test(seg.property)) {
|
|
990
|
+
memberAccess = `.${seg.property}`;
|
|
991
|
+
} else {
|
|
992
|
+
memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
|
|
993
|
+
}
|
|
958
994
|
result += `[?(@${memberAccess}==${formatFilterLiteral(seg.value)})]`;
|
|
959
995
|
break;
|
|
960
996
|
}
|
|
@@ -971,7 +1007,7 @@ function canonicalizeFilterForAtom(inner) {
|
|
|
971
1007
|
if (eqIdx === -1) return `[?(${inner})]`;
|
|
972
1008
|
const key = inner.slice(2, eqIdx);
|
|
973
1009
|
const valuePart = inner.slice(eqIdx);
|
|
974
|
-
if (
|
|
1010
|
+
if (NESTED_PATH_RE2.test(key)) {
|
|
975
1011
|
return `[?(@.${key}${valuePart})]`;
|
|
976
1012
|
}
|
|
977
1013
|
return `[?(@['${key.replace(/'/g, "''")}']${valuePart})]`;
|