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.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 = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*)'\]))=+'([^']+)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
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
- arrKey = result[4];
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: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey,
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 indexOfItemInArray = (arr, key, value) => {
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 && item[key] ? item[key].toString() === value.toString() : void 0) {
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) => el[change.embeddedKey]?.toString() === subchange.key.toString());
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) => el[change.embeddedKey]?.toString() === subchange.key.toString());
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
- function filterExpression(basePath, filterKey, filterValue) {
651
- const value = typeof filterValue === "number" ? filterValue : `'${filterValue}'`;
652
- const memberAccess = typeof filterKey === "string" && !IDENT_RE.test(filterKey) ? `['${filterKey}']` : `.${filterKey}`;
653
- return `${basePath}[?(@${memberAccess}==${value})]`;
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 || !SIMPLE_PROPERTY_RE.test(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
- const memberAccess = SIMPLE_PROPERTY_RE.test(seg.property) ? `.${seg.property}` : `['${seg.property.replace(/'/g, "''")}']`;
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 (SIMPLE_PROPERTY_RE.test(key)) {
1010
+ if (NESTED_PATH_RE2.test(key)) {
975
1011
  return `[?(@.${key}${valuePart})]`;
976
1012
  }
977
1013
  return `[?(@['${key.replace(/'/g, "''")}']${valuePart})]`;