@dafengzhen/deep-diff
v0.1.1
Published
Deep object comparison and diff engine with support for cyclic references, arrays, dates, and customizable options
Maintainers
Readme
deep-diff
一个零依赖的 TypeScript 深度对象比较与差异分析库。它能生成详细的差异树,支持循环引用、数组、日期、正则表达式以及可自定义的选项。
特性
- 零依赖 — 轻量且快速
- 循环引用检测 — 安全比较包含循环引用的对象
- 差异树输出 — 结构化的、可遍历的差异结果树
- 差异统计 — 新增、删除、修改、未变更节点的计数
- 高度可定制 — 可配置最大深度、忽略的键、键排序等
- Date 与 RegExp 支持 — 正确处理
Date和RegExp的值比较 - TypeScript 优先 — 完整类型定义,开箱即用
安装
npm install @dafengzhen/deep-diffyarn add @dafengzhen/deep-diffpnpm add @dafengzhen/deep-diff使用
基本差异比较
import { diff, DiffType } from '@dafengzhen/deep-diff';
const oldObj = { name: 'Alice', age: 30 };
const newObj = { name: 'Alice', age: 31 };
const result = diff(oldObj, newObj);
console.log(result.changed); // true
console.log(result.children[0].diffType); // DiffType.Equal
console.log(result.children[1].diffType); // DiffType.Modified深度相等检查
import { deepEqual } from '@dafengzhen/deep-diff';
deepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); // true
deepEqual({ a: 1 }, { a: 2 }); // false查找所有差异
import { diff, findDifferences } from '@dafengzhen/deep-diff';
const result = diff(
{ name: 'Alice', age: 30, city: 'NYC' },
{ name: 'Alice', age: 31, city: 'LA' },
);
const changes = findDifferences(result);
// [
// { key: 'age', diffType: 'modified', ... },
// { key: 'city', diffType: 'modified', ... }
// ]获取变更路径
import { diff, getChangedPaths } from '@dafengzhen/deep-diff';
const result = diff({ user: { name: 'Alice', age: 30 } }, { user: { name: 'Bob', age: 30 } });
console.log(getChangedPaths(result)); // ['user.name']获取差异统计
import { diff, getDiffStats } from '@dafengzhen/deep-diff';
const result = diff({ a: 1, b: 2, c: 3 }, { a: 1, b: 20, d: 4 });
console.log(getDiffStats(result));
// { total: 5, added: 1, removed: 1, modified: 1, equal: 2 }检查是否存在变更
import { diff, hasChanges } from '@dafengzhen/deep-diff';
hasChanges(diff({ a: 1 }, { a: 1 })); // false
hasChanges(diff({ a: 1 }, { a: 2 })); // true使用选项
import { diff } from '@dafengzhen/deep-diff';
const result = diff(
{ stable: true, updatedAt: '2026-01-01' },
{ stable: true, updatedAt: '2026-01-02' },
'root',
[],
{
ignoreKeys: ['updatedAt'], // 比较时忽略的键
maxDepth: 5, // 最大遍历深度
sortKeys: true, // 对对象键进行排序以保持一致的输出
},
);循环引用
import { diff } from '@dafengzhen/deep-diff';
const left: any = { value: 1 };
const right: any = { value: 2 };
left.self = left;
right.self = right;
const result = diff(left, right);
// 安全处理循环引用,不会导致无限递归API
diff(oldValue, newValue, key?, path?, options?, depth?)
计算两个值之间的差异,返回 DiffNode 树。
| 参数 | 类型 | 默认值 | 描述 |
| ---------- | ---------------------- | -------- | ---------------------- |
| oldValue | unknown | — | 原始值 |
| newValue | unknown | — | 用于比较的新值 |
| key | number \| string | 'root' | 该值在父级中的键或索引 |
| path | (number \| string)[] | [] | 该值的完整路径 |
| options | DiffOptions | {} | 差异比较的配置选项 |
| depth | number | 0 | 当前嵌套深度 |
deepEqual(left, right)
对两个值进行深度相等比较。如果深度相等则返回 true。
findDifferences(node)
查找差异树中所有包含变更的节点。返回 DiffNode 对象数组。
getChangedPaths(node)
获取所有变更节点的字符串路径(例如 'user.address[0].city')。
getDiffStats(node)
计算差异树中差异的统计信息。返回 { total, added, removed, modified, equal }。
getNodeAtPath(node, path)
从差异树中获取指定路径的 DiffNode。
hasChanges(node)
检查差异树中是否存在任何变更。
类型
DiffNode<T>
| 属性 | 类型 | 描述 |
| ----------- | ------------------ | ------------------------ |
| changed | boolean | 该节点是否表示存在变更 |
| children | DiffNode[] | 子差异节点 |
| diffType | DiffType | 差异类型 |
| key | number \| string | 该节点在父级中的键或索引 |
| leaf | boolean | 是否为叶子节点 |
| newValue? | T | 新值(如果可用) |
| oldValue? | T | 原始值(如果可用) |
| path | DiffPath | 该节点的完整路径 |
| valueType | ValueType | 值的类型 |
DiffType(枚举)
Added— 值被新增(仅存在于新值中)Equal— 值无差异Modified— 值被修改Removed— 值被删除(仅存在于旧值中)
DiffOptions
| 属性 | 类型 | 默认值 | 描述 |
| ---------------- | ---------- | ------ | ------------------------------------ |
| arrayMatchKey? | string | — | 用于匹配数组元素的键 |
| ignoreKeys? | string[] | — | 比较时忽略的键 |
| maxDepth? | number | — | 最大遍历深度(默认无限制) |
| sortKeys? | boolean | true | 是否对对象键进行排序以保持一致的输出 |
