@astech/deepdiff
Version:
A fast, lightweight TypeScript library for comparing deeply nested objects and detecting changes with detailed diff information.
193 lines (192 loc) • 5.87 kB
JavaScript
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 index_exports = {};
__export(index_exports, {
compare: () => compare,
deepDiff: () => deepDiff,
filterByType: () => filterByType,
getChangedPaths: () => getChangedPaths,
getSummary: () => getSummary,
groupByType: () => groupByType,
isDeepEqual: () => isDeepEqual
});
module.exports = __toCommonJS(index_exports);
function buildDescriptionPath(path) {
return path.map((p) => {
const arrayIndex = /^\[(\d+)\]$/.exec(p);
if (arrayIndex) return `index ${arrayIndex[1]}`;
return `"${p}"`;
}).join(" \u2192 ");
}
function isEqual(a, b, options = {}) {
if (a === b) return true;
if (options.customEqual) {
return options.customEqual(a, b);
}
if (options.ignoreCase && typeof a === "string" && typeof b === "string") {
return a.toLowerCase() === b.toLowerCase();
}
return false;
}
function* deepDiff(a, b, path = [], visited = /* @__PURE__ */ new WeakSet(), options = {}, depth = 0) {
if (options.maxDepth && depth > options.maxDepth) {
return;
}
if (isEqual(a, b, options)) return;
const joinedPath = path.join(".");
const descPath = buildDescriptionPath(path);
if (typeof a === "object" && a && typeof b === "object" && b) {
if (visited.has(a) || visited.has(b)) {
return;
}
visited.add(a);
visited.add(b);
}
if (typeof a !== typeof b) {
if (options.includeNulls === false && (a == null || b == null)) {
return;
}
yield {
path: joinedPath,
type: "changed",
before: a,
after: b,
summary: `Changed ${joinedPath} from ${JSON.stringify(a)} to ${JSON.stringify(b)}`,
description: `Field ${descPath} changed from ${JSON.stringify(a)} to ${JSON.stringify(b)}.`
};
return;
}
if (Array.isArray(a) && Array.isArray(b)) {
const max = Math.max(a.length, b.length);
for (let i = 0; i < max; i++) {
yield* deepDiff(
a[i],
b[i],
[...path, `[${i}]`],
visited,
options,
depth + 1
);
}
return;
}
if (typeof a === "object" && a && b) {
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
for (const key of keys) {
if (options.ignoreKeys && options.ignoreKeys.includes(key)) {
continue;
}
const newPath = [...path, key];
const joinedKeyPath = newPath.join(".");
const descKeyPath = buildDescriptionPath(newPath);
if (!(key in a)) {
if (options.includeNulls === false && b[key] == null) {
continue;
}
yield {
path: joinedKeyPath,
type: "added",
after: b[key],
summary: `Added ${joinedKeyPath} = ${JSON.stringify(b[key])}`,
description: `Field ${descKeyPath} was added with value ${JSON.stringify(b[key])}.`
};
} else if (!(key in b)) {
if (options.includeNulls === false && a[key] == null) {
continue;
}
yield {
path: joinedKeyPath,
type: "removed",
before: a[key],
summary: `Removed ${joinedKeyPath} = ${JSON.stringify(a[key])}`,
description: `Field ${descKeyPath} was removed. Previous value was ${JSON.stringify(a[key])}.`
};
} else {
yield* deepDiff(a[key], b[key], newPath, visited, options, depth + 1);
}
}
} else {
if (options.includeNulls === false && (a == null || b == null)) {
return;
}
yield {
path: joinedPath,
type: "changed",
before: a,
after: b,
summary: `Changed ${joinedPath} from ${JSON.stringify(a)} to ${JSON.stringify(b)}`,
description: `Field ${descPath} changed from ${JSON.stringify(a)} to ${JSON.stringify(b)}.`
};
}
}
function compare(a, b, options = {}) {
if (!a || !b) {
throw new Error("Both objects must be provided for comparison");
}
if (typeof a !== "object" || typeof b !== "object") {
throw new Error("Both parameters must be objects for comparison");
}
return [...deepDiff(a, b, [], /* @__PURE__ */ new WeakSet(), options, 0)];
}
function filterByType(diffs, type) {
return diffs.filter((diff) => diff.type === type);
}
function groupByType(diffs) {
const result = {
added: [],
removed: [],
changed: []
};
return diffs.reduce((groups, diff) => {
groups[diff.type].push(diff);
return groups;
}, result);
}
function getSummary(diffs) {
const groups = groupByType(diffs);
return {
total: diffs.length,
added: groups.added?.length || 0,
removed: groups.removed?.length || 0,
changed: groups.changed?.length || 0
};
}
function isDeepEqual(a, b, options = {}) {
try {
const diffs = compare(a, b, options);
return diffs.length === 0;
} catch {
return false;
}
}
function getChangedPaths(diffs) {
return diffs.map((diff) => diff.path);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
compare,
deepDiff,
filterByType,
getChangedPaths,
getSummary,
groupByType,
isDeepEqual
});
//# sourceMappingURL=index.cjs.map
;