UNPKG

@astech/deepdiff

Version:

A fast, lightweight TypeScript library for comparing deeply nested objects and detecting changes with detailed diff information.

1 lines 10.4 kB
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// src/index.ts\nexport interface DiffResult {\n path: string;\n type: \"added\" | \"removed\" | \"changed\";\n before?: any;\n after?: any;\n summary: string;\n description: string;\n}\n\nexport interface CompareOptions {\n /** Include null/undefined values in the diff */\n includeNulls?: boolean;\n /** Custom function to determine if two values are equal */\n customEqual?: (a: any, b: any) => boolean;\n /** Maximum depth to traverse (prevents stack overflow) */\n maxDepth?: number;\n /** Keys to ignore during comparison */\n ignoreKeys?: string[];\n /** Whether to ignore case for string comparisons */\n ignoreCase?: boolean;\n}\n\nfunction buildDescriptionPath(path: string[]): string {\n return path\n .map((p) => {\n const arrayIndex = /^\\[(\\d+)\\]$/.exec(p);\n if (arrayIndex) return `index ${arrayIndex[1]}`;\n return `\"${p}\"`;\n })\n .join(\"\");\n}\n\nfunction isEqual(a: any, b: any, options: CompareOptions = {}): boolean {\n if (a === b) return true;\n\n if (options.customEqual) {\n return options.customEqual(a, b);\n }\n\n if (options.ignoreCase && typeof a === \"string\" && typeof b === \"string\") {\n return a.toLowerCase() === b.toLowerCase();\n }\n\n return false;\n}\n\nexport function* deepDiff(\n a: any,\n b: any,\n path: string[] = [],\n visited = new WeakSet(),\n options: CompareOptions = {},\n depth = 0\n): Generator<DiffResult> {\n // Check max depth\n if (options.maxDepth && depth > options.maxDepth) {\n return;\n }\n\n if (isEqual(a, b, options)) return;\n\n const joinedPath = path.join(\".\");\n const descPath = buildDescriptionPath(path);\n\n // Handle circular references\n if (typeof a === \"object\" && a && typeof b === \"object\" && b) {\n if (visited.has(a) || visited.has(b)) {\n return; // Skip circular references\n }\n visited.add(a);\n visited.add(b);\n }\n\n if (typeof a !== typeof b) {\n // Handle null/undefined values for type changes\n if (options.includeNulls === false && (a == null || b == null)) {\n return;\n }\n\n yield {\n path: joinedPath,\n type: \"changed\",\n before: a,\n after: b,\n summary: `Changed ${joinedPath} from ${JSON.stringify(a)} to ${JSON.stringify(b)}`,\n description: `Field ${descPath} changed from ${JSON.stringify(a)} to ${JSON.stringify(b)}.`,\n };\n return;\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n yield* deepDiff(\n a[i],\n b[i],\n [...path, `[${i}]`],\n visited,\n options,\n depth + 1\n );\n }\n return;\n }\n\n if (typeof a === \"object\" && a && b) {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]);\n for (const key of keys) {\n // Skip ignored keys\n if (options.ignoreKeys && options.ignoreKeys.includes(key)) {\n continue;\n }\n\n const newPath = [...path, key];\n const joinedKeyPath = newPath.join(\".\");\n const descKeyPath = buildDescriptionPath(newPath);\n\n if (!(key in a)) {\n // Handle null/undefined values for added case\n if (options.includeNulls === false && b[key] == null) {\n continue;\n }\n yield {\n path: joinedKeyPath,\n type: \"added\",\n after: b[key],\n summary: `Added ${joinedKeyPath} = ${JSON.stringify(b[key])}`,\n description: `Field ${descKeyPath} was added with value ${JSON.stringify(b[key])}.`,\n };\n } else if (!(key in b)) {\n // Handle null/undefined values for removed case\n if (options.includeNulls === false && a[key] == null) {\n continue;\n }\n yield {\n path: joinedKeyPath,\n type: \"removed\",\n before: a[key],\n summary: `Removed ${joinedKeyPath} = ${JSON.stringify(a[key])}`,\n description: `Field ${descKeyPath} was removed. Previous value was ${JSON.stringify(a[key])}.`,\n };\n } else {\n yield* deepDiff(a[key], b[key], newPath, visited, options, depth + 1);\n }\n }\n } else {\n // Handle null/undefined values based on options\n if (options.includeNulls === false && (a == null || b == null)) {\n return;\n }\n\n yield {\n path: joinedPath,\n type: \"changed\",\n before: a,\n after: b,\n summary: `Changed ${joinedPath} from ${JSON.stringify(a)} to ${JSON.stringify(b)}`,\n description: `Field ${descPath} changed from ${JSON.stringify(a)} to ${JSON.stringify(b)}.`,\n };\n }\n}\n\nexport function compare(\n a: any,\n b: any,\n options: CompareOptions = {}\n): DiffResult[] {\n if (!a || !b) {\n throw new Error(\"Both objects must be provided for comparison\");\n }\n\n if (typeof a !== \"object\" || typeof b !== \"object\") {\n throw new Error(\"Both parameters must be objects for comparison\");\n }\n\n return [...deepDiff(a, b, [], new WeakSet(), options, 0)];\n}\n\n/**\n * Filter diff results by type\n */\nexport function filterByType(\n diffs: DiffResult[],\n type: DiffResult[\"type\"]\n): DiffResult[] {\n return diffs.filter((diff) => diff.type === type);\n}\n\n/**\n * Group diff results by type\n */\nexport function groupByType(\n diffs: DiffResult[]\n): Record<DiffResult[\"type\"], DiffResult[]> {\n const result = {\n added: [] as DiffResult[],\n removed: [] as DiffResult[],\n changed: [] as DiffResult[],\n };\n\n return diffs.reduce((groups, diff) => {\n groups[diff.type].push(diff);\n return groups;\n }, result);\n}\n\n/**\n * Get a summary of changes\n */\nexport function getSummary(diffs: DiffResult[]): {\n total: number;\n added: number;\n removed: number;\n changed: number;\n} {\n const groups = groupByType(diffs);\n return {\n total: diffs.length,\n added: groups.added?.length || 0,\n removed: groups.removed?.length || 0,\n changed: groups.changed?.length || 0,\n };\n}\n\n/**\n * Check if objects are deeply equal\n */\nexport function isDeepEqual(\n a: any,\n b: any,\n options: CompareOptions = {}\n): boolean {\n try {\n const diffs = compare(a, b, options);\n return diffs.length === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Get only the paths that changed\n */\nexport function getChangedPaths(diffs: DiffResult[]): string[] {\n return diffs.map((diff) => diff.path);\n}\n"],"mappings":"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","names":[]}