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 CHANGED
@@ -167,12 +167,12 @@ var revertChangeset = (obj, changeset) => {
167
167
  }
168
168
  return obj;
169
169
  };
170
- var atomizeChangeset = (obj, path = "$", embeddedKey) => {
170
+ var atomizeChangeset = (obj, path = "$", embeddedKey, embeddedKeyIsPath) => {
171
171
  if (Array.isArray(obj)) {
172
- return handleArray(obj, path, embeddedKey);
172
+ return handleArray(obj, path, embeddedKey, embeddedKeyIsPath);
173
173
  } else if (obj.changes || embeddedKey) {
174
174
  if (embeddedKey) {
175
- const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path);
175
+ const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path, embeddedKeyIsPath);
176
176
  path = updatedPath;
177
177
  if (atomicChange) {
178
178
  return atomicChange;
@@ -180,7 +180,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
180
180
  } else {
181
181
  path = append(path, obj.key);
182
182
  }
183
- return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey);
183
+ return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey, obj.embeddedKeyIsPath);
184
184
  } else {
185
185
  const valueType = getTypeOfObj(obj.value);
186
186
  let finalPath = path;
@@ -207,7 +207,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
207
207
  ];
208
208
  }
209
209
  };
210
- function handleEmbeddedKey(embeddedKey, obj, path) {
210
+ function handleEmbeddedKey(embeddedKey, obj, path, isPath) {
211
211
  if (embeddedKey === "$index") {
212
212
  path = `${path}[${obj.key}]`;
213
213
  return [path];
@@ -225,12 +225,12 @@ function handleEmbeddedKey(embeddedKey, obj, path) {
225
225
  ]
226
226
  ];
227
227
  } else {
228
- path = filterExpression(path, embeddedKey, obj.key);
228
+ path = filterExpression(path, embeddedKey, obj.key, isPath);
229
229
  return [path];
230
230
  }
231
231
  }
232
- var handleArray = (obj, path, embeddedKey) => {
233
- return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey)], []);
232
+ var handleArray = (obj, path, embeddedKey, embeddedKeyIsPath) => {
233
+ return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey, embeddedKeyIsPath)], []);
234
234
  };
235
235
  var unatomizeChangeset = (changes) => {
236
236
  if (!Array.isArray(changes)) {
@@ -250,15 +250,17 @@ var unatomizeChangeset = (changes) => {
250
250
  } else {
251
251
  for (let i = 1; i < segments.length; i++) {
252
252
  const segment = segments[i];
253
- const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*)'\]))=+'([^']+)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
253
+ const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*(?:''[^']*)*)'\]))=+'([^']*(?:''[^']*)*)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
254
254
  if (result) {
255
255
  let key;
256
256
  let embeddedKey;
257
257
  let arrKey;
258
+ let isPath;
258
259
  if (result[1]) {
259
260
  key = result[1];
260
- embeddedKey = result[3] || result[2] || "$value";
261
- arrKey = result[4];
261
+ embeddedKey = result[3]?.replace(/''/g, "'") || result[2] || "$value";
262
+ isPath = !result[3] && !!result[2] && result[2].includes(".") && NESTED_PATH_RE.test(result[2]) ? true : void 0;
263
+ arrKey = result[4]?.replace(/''/g, "'");
262
264
  } else {
263
265
  key = result[5];
264
266
  embeddedKey = "$index";
@@ -267,6 +269,7 @@ var unatomizeChangeset = (changes) => {
267
269
  if (i === segments.length - 1) {
268
270
  ptr.key = key;
269
271
  ptr.embeddedKey = embeddedKey;
272
+ if (isPath) ptr.embeddedKeyIsPath = true;
270
273
  ptr.type = "UPDATE" /* UPDATE */;
271
274
  ptr.changes = [
272
275
  {
@@ -279,6 +282,7 @@ var unatomizeChangeset = (changes) => {
279
282
  } else {
280
283
  ptr.key = key;
281
284
  ptr.embeddedKey = embeddedKey;
285
+ if (isPath) ptr.embeddedKeyIsPath = true;
282
286
  ptr.type = "UPDATE" /* UPDATE */;
283
287
  const newPtr = {};
284
288
  ptr.changes = [
@@ -480,12 +484,15 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
480
484
  const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
481
485
  const indexedNewObj = convertArrayToObj(newObj, uniqKey);
482
486
  const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options);
487
+ const isFunctionKey = typeof uniqKey === "function" && uniqKey.length === 2;
483
488
  if (diffs.length) {
489
+ const resolvedKey = isFunctionKey ? uniqKey(newObj[0] ?? oldObj[0], true) : uniqKey;
484
490
  return [
485
491
  {
486
492
  type: "UPDATE" /* UPDATE */,
487
493
  key: getKey(path),
488
- embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey,
494
+ embeddedKey: resolvedKey,
495
+ ...isFunctionKey && typeof resolvedKey === "string" && NESTED_PATH_RE.test(resolvedKey) && resolvedKey.includes(".") ? { embeddedKeyIsPath: true } : {},
489
496
  changes: diffs
490
497
  }
491
498
  ];
@@ -543,13 +550,13 @@ var comparePrimitives = (oldObj, newObj, path) => {
543
550
  }
544
551
  return changes;
545
552
  };
546
- var removeKey = (obj, key, embeddedKey) => {
553
+ var removeKey = (obj, key, embeddedKey, isPath) => {
547
554
  if (Array.isArray(obj)) {
548
555
  if (embeddedKey === "$index") {
549
556
  obj.splice(Number(key), 1);
550
557
  return;
551
558
  }
552
- const index = indexOfItemInArray(obj, embeddedKey, key);
559
+ const index = indexOfItemInArray(obj, embeddedKey, key, isPath);
553
560
  if (index === -1) {
554
561
  console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array!`);
555
562
  return;
@@ -560,13 +567,20 @@ var removeKey = (obj, key, embeddedKey) => {
560
567
  return;
561
568
  }
562
569
  };
563
- var indexOfItemInArray = (arr, key, value) => {
570
+ var resolveProperty = (obj, key, isPath) => {
571
+ if (obj == null) return void 0;
572
+ if (typeof key !== "string" || !isPath || !key.includes(".")) return obj[key];
573
+ return key.split(".").reduce((cur, seg) => cur?.[seg], obj);
574
+ };
575
+ var indexOfItemInArray = (arr, key, value, isPath) => {
564
576
  if (key === "$value") {
565
577
  return arr.indexOf(value);
566
578
  }
567
579
  for (let i = 0; i < arr.length; i++) {
568
580
  const item = arr[i];
569
- if (item && item[key] ? item[key].toString() === value.toString() : void 0) {
581
+ if (item == null) continue;
582
+ const resolved = resolveProperty(item, key, isPath);
583
+ if (resolved != null && String(resolved) === String(value)) {
570
584
  return i;
571
585
  }
572
586
  }
@@ -584,7 +598,7 @@ var addKeyValue = (obj, key, value, embeddedKey) => {
584
598
  return obj ? obj[key] = value : null;
585
599
  }
586
600
  };
587
- var applyLeafChange = (obj, change, embeddedKey) => {
601
+ var applyLeafChange = (obj, change, embeddedKey, isPath) => {
588
602
  const { type, key, value } = change;
589
603
  switch (type) {
590
604
  case "ADD" /* ADD */:
@@ -592,7 +606,7 @@ var applyLeafChange = (obj, change, embeddedKey) => {
592
606
  case "UPDATE" /* UPDATE */:
593
607
  return modifyKeyValue(obj, key, value);
594
608
  case "REMOVE" /* REMOVE */:
595
- return removeKey(obj, key, embeddedKey);
609
+ return removeKey(obj, key, embeddedKey, isPath);
596
610
  }
597
611
  };
598
612
  var applyArrayChange = (arr, change) => {
@@ -609,7 +623,7 @@ var applyArrayChange = (arr, change) => {
609
623
  }
610
624
  for (const subchange of changes) {
611
625
  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 */) {
612
- applyLeafChange(arr, subchange, change.embeddedKey);
626
+ applyLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
613
627
  } else {
614
628
  let element;
615
629
  if (change.embeddedKey === "$index") {
@@ -620,7 +634,10 @@ var applyArrayChange = (arr, change) => {
620
634
  element = arr[index];
621
635
  }
622
636
  } else {
623
- element = arr.find((el) => el[change.embeddedKey]?.toString() === subchange.key.toString());
637
+ element = arr.find((el) => {
638
+ const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
639
+ return resolved != null && String(resolved) === String(subchange.key);
640
+ });
624
641
  }
625
642
  if (element) {
626
643
  applyChangeset(element, subchange.changes);
@@ -636,7 +653,7 @@ var applyBranchChange = (obj, change) => {
636
653
  return applyChangeset(obj, change.changes);
637
654
  }
638
655
  };
639
- var revertLeafChange = (obj, change, embeddedKey = "$index") => {
656
+ var revertLeafChange = (obj, change, embeddedKey = "$index", isPath) => {
640
657
  const { type, key, value, oldValue } = change;
641
658
  if (key === "$root") {
642
659
  switch (type) {
@@ -666,7 +683,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
666
683
  }
667
684
  switch (type) {
668
685
  case "ADD" /* ADD */:
669
- return removeKey(obj, key, embeddedKey);
686
+ return removeKey(obj, key, embeddedKey, isPath);
670
687
  case "UPDATE" /* UPDATE */:
671
688
  return modifyKeyValue(obj, key, oldValue);
672
689
  case "REMOVE" /* REMOVE */:
@@ -676,7 +693,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
676
693
  var revertArrayChange = (arr, change) => {
677
694
  for (const subchange of change.changes) {
678
695
  if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) {
679
- revertLeafChange(arr, subchange, change.embeddedKey);
696
+ revertLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
680
697
  } else {
681
698
  let element;
682
699
  if (change.embeddedKey === "$index") {
@@ -687,7 +704,10 @@ var revertArrayChange = (arr, change) => {
687
704
  element = arr[index];
688
705
  }
689
706
  } else {
690
- element = arr.find((el) => el[change.embeddedKey]?.toString() === subchange.key.toString());
707
+ element = arr.find((el) => {
708
+ const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
709
+ return resolved != null && String(resolved) === String(subchange.key);
710
+ });
691
711
  }
692
712
  if (element) {
693
713
  revertChangeset(element, subchange.changes);
@@ -707,10 +727,18 @@ function append(basePath, nextSegment) {
707
727
  return nextSegment.includes(".") ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
708
728
  }
709
729
  var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
710
- function filterExpression(basePath, filterKey, filterValue) {
711
- const value = typeof filterValue === "number" ? filterValue : `'${filterValue}'`;
712
- const memberAccess = typeof filterKey === "string" && !IDENT_RE.test(filterKey) ? `['${filterKey}']` : `.${filterKey}`;
713
- return `${basePath}[?(@${memberAccess}==${value})]`;
730
+ var NESTED_PATH_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
731
+ function filterExpression(basePath, filterKey, filterValue, isPath) {
732
+ const escapedValue = `'${filterValue.replace(/'/g, "''")}'`;
733
+ let memberAccess;
734
+ if (isPath && NESTED_PATH_RE.test(filterKey)) {
735
+ memberAccess = "." + filterKey;
736
+ } else if (IDENT_RE.test(filterKey)) {
737
+ memberAccess = `.${filterKey}`;
738
+ } else {
739
+ memberAccess = `['${filterKey.replace(/'/g, "''")}']`;
740
+ }
741
+ return `${basePath}[?(@${memberAccess}==${escapedValue})]`;
714
742
  }
715
743
 
716
744
  // src/jsonCompare.ts
@@ -927,12 +955,13 @@ function findFilterClose(s, from) {
927
955
  }
928
956
  return -1;
929
957
  }
958
+ var NESTED_PATH_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
930
959
  function parseFilter(inner) {
931
960
  if (inner.startsWith("@.")) {
932
961
  const eq = inner.indexOf("==");
933
962
  if (eq === -1) throw new Error(`Invalid filter: missing '==' in ${inner}`);
934
963
  const key = inner.slice(2, eq);
935
- if (!key || !SIMPLE_PROPERTY_RE.test(key)) {
964
+ if (!key || !NESTED_PATH_RE2.test(key)) {
936
965
  throw new Error(`Invalid property name in filter: '${key}'. Use bracket notation for non-identifier keys: @['${key}']`);
937
966
  }
938
967
  return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(eq + 2)) };
@@ -940,7 +969,7 @@ function parseFilter(inner) {
940
969
  if (inner.startsWith("@['")) {
941
970
  const [key, endIdx] = extractQuotedString(inner, 3);
942
971
  const valStart = endIdx + 4;
943
- return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)) };
972
+ return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)), literalKey: true };
944
973
  }
945
974
  if (inner.startsWith("@==")) {
946
975
  return { type: "valueFilter", value: parseFilterLiteral(inner.slice(3)) };
@@ -1014,7 +1043,14 @@ function buildAtomPath(segments) {
1014
1043
  result += `[${seg.index}]`;
1015
1044
  break;
1016
1045
  case "keyFilter": {
1017
- const memberAccess = SIMPLE_PROPERTY_RE.test(seg.property) ? `.${seg.property}` : `['${seg.property.replace(/'/g, "''")}']`;
1046
+ let memberAccess;
1047
+ if (seg.literalKey) {
1048
+ memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
1049
+ } else if (NESTED_PATH_RE2.test(seg.property)) {
1050
+ memberAccess = `.${seg.property}`;
1051
+ } else {
1052
+ memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
1053
+ }
1018
1054
  result += `[?(@${memberAccess}==${formatFilterLiteral(seg.value)})]`;
1019
1055
  break;
1020
1056
  }
@@ -1031,7 +1067,7 @@ function canonicalizeFilterForAtom(inner) {
1031
1067
  if (eqIdx === -1) return `[?(${inner})]`;
1032
1068
  const key = inner.slice(2, eqIdx);
1033
1069
  const valuePart = inner.slice(eqIdx);
1034
- if (SIMPLE_PROPERTY_RE.test(key)) {
1070
+ if (NESTED_PATH_RE2.test(key)) {
1035
1071
  return `[?(@.${key}${valuePart})]`;
1036
1072
  }
1037
1073
  return `[?(@['${key.replace(/'/g, "''")}']${valuePart})]`;