"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { CompareOperation: () => CompareOperation, Operation: () => Operation, applyChangelist: () => applyChangelist, applyChangeset: () => applyChangeset, atomizeChangeset: () => atomizeChangeset, compare: () => compare2, createContainer: () => createContainer, createValue: () => createValue, diff: () => diff, enrich: () => enrich, getTypeOfObj: () => getTypeOfObj, revertChangeset: () => revertChangeset, unatomizeChangeset: () => unatomizeChangeset }); module.exports = __toCommonJS(src_exports); // src/jsonDiff.ts var import_lodash = require("lodash"); // src/helpers.ts function splitJSONPath(path) { let parts = []; let currentPart = ""; let inSingleQuotes = false; let inBrackets = 0; for (let i = 0; i < path.length; i++) { const char = path[i]; if (char === "'" && path[i - 1] !== "\\") { inSingleQuotes = !inSingleQuotes; } else if (char === "[" && !inSingleQuotes) { inBrackets++; } else if (char === "]" && !inSingleQuotes) { inBrackets--; } if (char === "." && !inSingleQuotes && inBrackets === 0) { parts.push(currentPart); currentPart = ""; } else { currentPart += char; } } if (currentPart !== "") { parts.push(currentPart); } return parts; } // src/jsonDiff.ts var Operation = /* @__PURE__ */ ((Operation2) => { Operation2["REMOVE"] = "REMOVE"; Operation2["ADD"] = "ADD"; Operation2["UPDATE"] = "UPDATE"; return Operation2; })(Operation || {}); function diff(oldObj, newObj, options = {}) { let { embeddedObjKeys, keysToSkip, treatTypeChangeAsReplace } = options; if (embeddedObjKeys instanceof Map) { embeddedObjKeys = new Map( Array.from(embeddedObjKeys.entries()).map(([key, value]) => [ key instanceof RegExp ? key : key.replace(/^\./, ""), value ]) ); } else if (embeddedObjKeys) { embeddedObjKeys = Object.fromEntries( Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ""), value]) ); } return compare(oldObj, newObj, [], [], { embeddedObjKeys, keysToSkip: keysToSkip ?? [], treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true }); } var applyChangeset = (obj, changeset) => { if (changeset) { changeset.forEach((change) => { const { type, key, value, embeddedKey } = change; if (value !== null && value !== void 0 || type === "REMOVE" /* REMOVE */) { applyLeafChange(obj, change, embeddedKey); } else { applyBranchChange(obj[key], change); } }); } return obj; }; var revertChangeset = (obj, changeset) => { if (changeset) { changeset.reverse().forEach( (change) => !change.changes ? revertLeafChange(obj, change) : revertBranchChange(obj[change.key], change) ); } return obj; }; var atomizeChangeset = (obj, path = "$", embeddedKey) => { if (Array.isArray(obj)) { return handleArray(obj, path, embeddedKey); } else if (obj.changes || embeddedKey) { if (embeddedKey) { const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path); path = updatedPath; if (atomicChange) { return atomicChange; } } else { path = append(path, obj.key); } return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey); } else { const valueType = getTypeOfObj(obj.value); return [ { ...obj, path: valueType === "Object" || path.endsWith(`[${obj.key}]`) ? path : append(path, obj.key), valueType } ]; } }; function handleEmbeddedKey(embeddedKey, obj, path) { if (embeddedKey === "$index") { path = `${path}[${obj.key}]`; return [path]; } else if (embeddedKey === "$value") { path = `${path}[?(@=='${obj.key}')]`; const valueType = getTypeOfObj(obj.value); return [ path, [ { ...obj, path, valueType } ] ]; } else if (obj.type === "ADD" /* ADD */) { return [path]; } else { path = filterExpression(path, embeddedKey, obj.key); return [path]; } } var handleArray = (obj, path, embeddedKey) => { return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey)], []); }; var unatomizeChangeset = (changes) => { if (!Array.isArray(changes)) { changes = [changes]; } const changesArr = []; changes.forEach((change) => { const obj = {}; let ptr = obj; const segments = splitJSONPath(change.path); if (segments.length === 1) { ptr.key = change.key; ptr.type = change.type; ptr.value = change.value; ptr.oldValue = change.oldValue; changesArr.push(ptr); } else { for (let i = 1; i < segments.length; i++) { const segment = segments[i]; const result = /^([^[\]]+)\[\?\(@\.?([^=]*)=+'([^']+)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment); if (result) { let key; let embeddedKey; let arrKey; if (result[1]) { key = result[1]; embeddedKey = result[2] || "$value"; arrKey = result[3]; } else { key = result[4]; embeddedKey = "$index"; arrKey = Number(result[5]); } if (i === segments.length - 1) { ptr.key = key; ptr.embeddedKey = embeddedKey; ptr.type = "UPDATE" /* UPDATE */; ptr.changes = [ { type: change.type, key: arrKey, value: change.value, oldValue: change.oldValue } ]; } else { ptr.key = key; ptr.embeddedKey = embeddedKey; ptr.type = "UPDATE" /* UPDATE */; const newPtr = {}; ptr.changes = [ { type: "UPDATE" /* UPDATE */, key: arrKey, changes: [newPtr] } ]; ptr = newPtr; } } else { if (i === segments.length - 1) { if (change.value !== null && change.valueType === "Object") { ptr.key = segment; ptr.type = "UPDATE" /* UPDATE */; ptr.changes = [ { key: change.key, type: change.type, value: change.value } ]; } else { ptr.key = change.key; ptr.type = change.type; ptr.value = change.value; ptr.oldValue = change.oldValue; } } else { ptr.key = segment; ptr.type = "UPDATE" /* UPDATE */; const newPtr = {}; ptr.changes = [newPtr]; ptr = newPtr; } } } changesArr.push(obj); } }); return changesArr; }; var getTypeOfObj = (obj) => { if (typeof obj === "undefined") { return "undefined"; } if (obj === null) { return null; } return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1]; }; var getKey = (path) => { const left = path[path.length - 1]; return left != null ? left : "$root"; }; var compare = (oldObj, newObj, path, keyPath, options) => { let changes = []; const typeOfOldObj = getTypeOfObj(oldObj); const typeOfNewObj = getTypeOfObj(newObj); if (options.treatTypeChangeAsReplace && typeOfOldObj !== typeOfNewObj) { changes.push({ type: "REMOVE" /* REMOVE */, key: getKey(path), value: oldObj }); if (typeOfNewObj !== "undefined") { changes.push({ type: "ADD" /* ADD */, key: getKey(path), value: newObj }); } return changes; } if (typeOfNewObj === "undefined" && typeOfOldObj !== "undefined") { changes.push({ type: "REMOVE" /* REMOVE */, key: getKey(path), value: oldObj }); return changes; } if (typeOfNewObj === "Object" && typeOfOldObj === "Array") { changes.push({ type: "UPDATE" /* UPDATE */, key: getKey(path), value: newObj, oldValue: oldObj }); return changes; } switch (typeOfOldObj) { case "Date": changes = changes.concat( comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({ ...x, value: new Date(x.value), oldValue: new Date(x.oldValue) })) ); break; case "Object": { const diffs = compareObject(oldObj, newObj, path, keyPath, false, options); if (diffs.length) { if (path.length) { changes.push({ type: "UPDATE" /* UPDATE */, key: getKey(path), changes: diffs }); } else { changes = changes.concat(diffs); } } break; } case "Array": changes = changes.concat(compareArray(oldObj, newObj, path, keyPath, options)); break; case "Function": break; default: changes = changes.concat(comparePrimitives(oldObj, newObj, path)); } return changes; }; var compareObject = (oldObj, newObj, path, keyPath, skipPath = false, options = {}) => { let k; let newKeyPath; let newPath; if (skipPath == null) { skipPath = false; } let changes = []; const oldObjKeys = Object.keys(oldObj).filter((key) => options.keysToSkip.indexOf(key) === -1); const newObjKeys = Object.keys(newObj).filter((key) => options.keysToSkip.indexOf(key) === -1); const intersectionKeys = (0, import_lodash.intersection)(oldObjKeys, newObjKeys); for (k of intersectionKeys) { newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); const diffs = compare(oldObj[k], newObj[k], newPath, newKeyPath, options); if (diffs.length) { changes = changes.concat(diffs); } } const addedKeys = (0, import_lodash.difference)(newObjKeys, oldObjKeys); for (k of addedKeys) { newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); changes.push({ type: "ADD" /* ADD */, key: getKey(newPath), value: newObj[k] }); } const deletedKeys = (0, import_lodash.difference)(oldObjKeys, newObjKeys); for (k of deletedKeys) { newPath = path.concat([k]); newKeyPath = skipPath ? keyPath : keyPath.concat([k]); changes.push({ type: "REMOVE" /* REMOVE */, key: getKey(newPath), value: oldObj[k] }); } return changes; }; var compareArray = (oldObj, newObj, path, keyPath, options) => { if (getTypeOfObj(newObj) !== "Array") { return [{ type: "UPDATE" /* UPDATE */, key: getKey(path), value: newObj, oldValue: oldObj }]; } const left = getObjectKey(options.embeddedObjKeys, keyPath); const uniqKey = left != null ? left : "$index"; const indexedOldObj = convertArrayToObj(oldObj, uniqKey); const indexedNewObj = convertArrayToObj(newObj, uniqKey); const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options); if (diffs.length) { return [ { type: "UPDATE" /* UPDATE */, key: getKey(path), embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey, changes: diffs } ]; } else { return []; } }; var getObjectKey = (embeddedObjKeys, keyPath) => { if (embeddedObjKeys != null) { const path = keyPath.join("."); if (embeddedObjKeys instanceof Map) { for (const [key2, value] of embeddedObjKeys.entries()) { if (key2 instanceof RegExp) { if (path.match(key2)) { return value; } } else if (path === key2) { return value; } } } const key = embeddedObjKeys[path]; if (key != null) { return key; } } return void 0; }; var convertArrayToObj = (arr, uniqKey) => { let obj = {}; if (uniqKey === "$value") { arr.forEach((value) => { obj[value] = value; }); } else if (uniqKey !== "$index") { obj = (0, import_lodash.keyBy)(arr, uniqKey); } else { for (let i = 0; i < arr.length; i++) { const value = arr[i]; obj[i] = value; } } return obj; }; var comparePrimitives = (oldObj, newObj, path) => { const changes = []; if (oldObj !== newObj) { changes.push({ type: "UPDATE" /* UPDATE */, key: getKey(path), value: newObj, oldValue: oldObj }); } return changes; }; var removeKey = (obj, key, embeddedKey) => { if (Array.isArray(obj)) { if (embeddedKey === "$index") { obj.splice(key); return; } const index = indexOfItemInArray(obj, embeddedKey, key); if (index === -1) { console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array'`); return; } return obj.splice(index != null ? index : key, 1); } else { obj[key] = void 0; delete obj[key]; return; } }; var indexOfItemInArray = (arr, key, value) => { if (key === "$value") { return arr.indexOf(value); } for (let i = 0; i < arr.length; i++) { const item = arr[i]; if (item && item[key] ? item[key].toString() === value.toString() : void 0) { return i; } } return -1; }; var modifyKeyValue = (obj, key, value) => obj[key] = value; var addKeyValue = (obj, key, value) => { if (Array.isArray(obj)) { return obj.push(value); } else { return obj ? obj[key] = value : null; } }; var applyLeafChange = (obj, change, embeddedKey) => { const { type, key, value } = change; switch (type) { case "ADD" /* ADD */: return addKeyValue(obj, key, value); case "UPDATE" /* UPDATE */: return modifyKeyValue(obj, key, value); case "REMOVE" /* REMOVE */: return removeKey(obj, key, embeddedKey); } }; var applyArrayChange = (arr, change) => (() => { const result = []; for (const subchange of change.changes) { if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) { result.push(applyLeafChange(arr, subchange, change.embeddedKey)); } else { let element; if (change.embeddedKey === "$index") { element = arr[subchange.key]; } else if (change.embeddedKey === "$value") { const index = arr.indexOf(subchange.key); if (index !== -1) { element = arr[index]; } } else { element = (0, import_lodash.find)(arr, (el) => el[change.embeddedKey]?.toString() === subchange.key.toString()); } result.push(applyChangeset(element, subchange.changes)); } } return result; })(); var applyBranchChange = (obj, change) => { if (Array.isArray(obj)) { return applyArrayChange(obj, change); } else { return applyChangeset(obj, change.changes); } }; var revertLeafChange = (obj, change, embeddedKey = "$index") => { const { type, key, value, oldValue } = change; switch (type) { case "ADD" /* ADD */: return removeKey(obj, key, embeddedKey); case "UPDATE" /* UPDATE */: return modifyKeyValue(obj, key, oldValue); case "REMOVE" /* REMOVE */: return addKeyValue(obj, key, value); } }; var revertArrayChange = (arr, change) => (() => { const result = []; for (const subchange of change.changes) { if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) { result.push(revertLeafChange(arr, subchange, change.embeddedKey)); } else { let element; if (change.embeddedKey === "$index") { element = arr[+subchange.key]; } else { element = (0, import_lodash.find)(arr, (el) => el[change.embeddedKey].toString() === subchange.key); } result.push(revertChangeset(element, subchange.changes)); } } return result; })(); var revertBranchChange = (obj, change) => { if (Array.isArray(obj)) { return revertArrayChange(obj, change); } else { return revertChangeset(obj, change.changes); } }; function append(basePath, nextSegment) { return nextSegment.includes(".") ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`; } function filterExpression(basePath, filterKey, filterValue) { const value = typeof filterValue === "number" ? filterValue : `'${filterValue}'`; return typeof filterKey === "string" && filterKey.includes(".") ? `${basePath}[?(@[${filterKey}]==${value})]` : `${basePath}[?(@.${filterKey}==${value})]`; } // src/jsonCompare.ts var import_lodash2 = require("lodash"); var CompareOperation = /* @__PURE__ */ ((CompareOperation2) => { CompareOperation2["CONTAINER"] = "CONTAINER"; CompareOperation2["UNCHANGED"] = "UNCHANGED"; return CompareOperation2; })(CompareOperation || {}); var createValue = (value) => ({ type: "UNCHANGED" /* UNCHANGED */, value }); var createContainer = (value) => ({ type: "CONTAINER" /* CONTAINER */, value }); var enrich = (object) => { const objectType = getTypeOfObj(object); switch (objectType) { case "Object": return (0, import_lodash2.keys)(object).map((key) => ({ key, value: enrich(object[key]) })).reduce((accumulator, entry) => { accumulator.value[entry.key] = entry.value; return accumulator; }, createContainer({})); case "Array": return (0, import_lodash2.chain)(object).map((value) => enrich(value)).reduce((accumulator, value) => { accumulator.value.push(value); return accumulator; }, createContainer([])).value(); case "Function": return void 0; case "Date": default: return createValue(object); } }; var applyChangelist = (object, changelist) => { (0, import_lodash2.chain)(changelist).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, "$.", ".") })).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, /(\[(?\d)\]\.)/g, "ARRVAL_START$ARRVAL_END") })).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, /(?\.)/g, ".value$") })).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, /\./, "") })).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, /ARRVAL_START/g, ".value[") })).map((entry) => ({ ...entry, path: (0, import_lodash2.replace)(entry.path, /ARRVAL_END/g, "].value.") })).value().forEach((entry) => { switch (entry.type) { case "ADD" /* ADD */: case "UPDATE" /* UPDATE */: (0, import_lodash2.set)(object, entry.path, { type: entry.type, value: entry.value, oldValue: entry.oldValue }); break; case "REMOVE" /* REMOVE */: (0, import_lodash2.set)(object, entry.path, { type: entry.type, value: void 0, oldValue: entry.value }); break; default: throw new Error(); } }); return object; }; var compare2 = (oldObject, newObject) => { return applyChangelist(enrich(oldObject), atomizeChangeset(diff(oldObject, newObject))); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CompareOperation, Operation, applyChangelist, applyChangeset, atomizeChangeset, compare, createContainer, createValue, diff, enrich, getTypeOfObj, revertChangeset, unatomizeChangeset }); //# sourceMappingURL=index.cjs.map