|
1 | | -const TYPES = { UNCHANGED: 'unchanged', ADDED: 'added', REMOVED: 'removed', MODIFIED: 'modified', TYPE_CHANGED: 'type_changed' } |
| 1 | +const TYPE = { UNCHANGED: 'unchanged', ADDED: 'added', REMOVED: 'removed', MODIFIED: 'modified', TYPE_CHANGED: 'type_changed' } |
2 | 2 |
|
3 | | -const getType = v => v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v |
| 3 | +const typeOf = v => v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v |
| 4 | +const isObj = v => v !== null && typeof v === 'object' |
| 5 | +const keys = (a, b) => [...new Set([...Object.keys(a || {}), ...Object.keys(b || {})])] |
4 | 6 |
|
5 | | -const isPrimitive = v => v === null || typeof v !== 'object' |
| 7 | +const node = (key, type, left, right, extra = {}) => ({ key, type, left, right, hasDiff: type !== TYPE.UNCHANGED, ...extra }) |
6 | 8 |
|
7 | | -const diffPrimitive = (left, right, key) => { |
8 | | - if (left === right) return { key, type: TYPES.UNCHANGED, left, right, hasDiff: false } |
9 | | - if (getType(left) !== getType(right)) return { key, type: TYPES.TYPE_CHANGED, left, right, hasDiff: true } |
10 | | - return { key, type: TYPES.MODIFIED, left, right, hasDiff: true } |
11 | | -} |
| 9 | +const container = (val, isArr) => isArr ? { isArray: true } : isObj(val) ? { isObject: true } : {} |
12 | 10 |
|
13 | | -const diffArray = (left, right, key) => { |
14 | | - const len = Math.max(left?.length || 0, right?.length || 0) |
15 | | - const children = Array.from({ length: len }, (_, i) => diff(left?.[i], right?.[i], i)) |
16 | | - const hasDiff = children.some(c => c.hasDiff) |
17 | | - return { key, type: hasDiff ? TYPES.MODIFIED : TYPES.UNCHANGED, left, right, children, hasDiff, isArray: true } |
18 | | -} |
| 11 | +const childMap = (val, side) => (v, k) => node( |
| 12 | + k, TYPE.UNCHANGED, |
| 13 | + side === 'added' ? undefined : v, |
| 14 | + side === 'added' ? v : undefined, |
| 15 | + isObj(v) && { children: mapChildren(v, side), ...container(v, Array.isArray(v)) } |
| 16 | +) |
19 | 17 |
|
20 | | -const diffObject = (left, right, key) => { |
21 | | - const keys = [...new Set([...Object.keys(left || {}), ...Object.keys(right || {})])] |
22 | | - const children = keys.map(k => diff(left?.[k], right?.[k], k)) |
23 | | - const hasDiff = children.some(c => c.hasDiff) |
24 | | - return { key, type: hasDiff ? TYPES.MODIFIED : TYPES.UNCHANGED, left, right, children, hasDiff, isObject: true } |
25 | | -} |
| 18 | +const mapChildren = (val, side) => |
| 19 | + Array.isArray(val) ? val.map(childMap(val, side)) : |
| 20 | + isObj(val) ? Object.entries(val).map(([k, v]) => childMap(val, side)(v, k)) : [] |
26 | 21 |
|
27 | | -const diff = (left, right, key = 'root') => { |
28 | | - if (left === undefined && right !== undefined) return { key, type: TYPES.ADDED, left, right, hasDiff: true, ...(!isPrimitive(right) && { children: diffChildren(right), isArray: Array.isArray(right), isObject: !Array.isArray(right) && typeof right === 'object' }) } |
29 | | - if (left !== undefined && right === undefined) return { key, type: TYPES.REMOVED, left, right, hasDiff: true, ...(!isPrimitive(left) && { children: diffChildren(left), isArray: Array.isArray(left), isObject: !Array.isArray(left) && typeof left === 'object' }) } |
30 | | - if (isPrimitive(left) && isPrimitive(right)) return diffPrimitive(left, right, key) |
31 | | - if (getType(left) !== getType(right)) return { key, type: TYPES.TYPE_CHANGED, left, right, hasDiff: true, children: [], isArray: Array.isArray(left) || Array.isArray(right), isObject: !Array.isArray(left) && typeof left === 'object' } |
32 | | - if (Array.isArray(left)) return diffArray(left, right, key) |
33 | | - return diffObject(left, right, key) |
| 22 | +const diffContainer = (left, right, key, isArr) => { |
| 23 | + const items = isArr |
| 24 | + ? Array.from({ length: Math.max(left?.length || 0, right?.length || 0) }, (_, i) => diff(left?.[i], right?.[i], i)) |
| 25 | + : keys(left, right).map(k => diff(left?.[k], right?.[k], k)) |
| 26 | + const hasDiff = items.some(c => c.hasDiff) |
| 27 | + return node(key, hasDiff ? TYPE.MODIFIED : TYPE.UNCHANGED, left, right, { children: items, ...container(left, isArr) }) |
34 | 28 | } |
35 | 29 |
|
36 | | -const diffChildren = val => { |
37 | | - if (Array.isArray(val)) return val.map((v, i) => ({ key: i, type: TYPES.UNCHANGED, left: v, right: v, hasDiff: false, ...(!isPrimitive(v) && { children: diffChildren(v), isArray: Array.isArray(v), isObject: !Array.isArray(v) && typeof v === 'object' }) })) |
38 | | - if (typeof val === 'object' && val !== null) return Object.entries(val).map(([k, v]) => ({ key: k, type: TYPES.UNCHANGED, left: v, right: v, hasDiff: false, ...(!isPrimitive(v) && { children: diffChildren(v), isArray: Array.isArray(v), isObject: !Array.isArray(v) && typeof v === 'object' }) })) |
39 | | - return [] |
| 30 | +const diff = (left, right, key = 'root') => { |
| 31 | + if (left === undefined) return node(key, TYPE.ADDED, left, right, isObj(right) && { children: mapChildren(right, 'added'), ...container(right, Array.isArray(right)) }) |
| 32 | + if (right === undefined) return node(key, TYPE.REMOVED, left, right, isObj(left) && { children: mapChildren(left, 'removed'), ...container(left, Array.isArray(left)) }) |
| 33 | + if (!isObj(left) && !isObj(right)) return node(key, left === right ? TYPE.UNCHANGED : typeOf(left) !== typeOf(right) ? TYPE.TYPE_CHANGED : TYPE.MODIFIED, left, right) |
| 34 | + if (typeOf(left) !== typeOf(right)) return node(key, TYPE.TYPE_CHANGED, left, right, { children: [], ...container(left, Array.isArray(left)) }) |
| 35 | + return diffContainer(left, right, key, Array.isArray(left)) |
40 | 36 | } |
41 | 37 |
|
42 | | -export { diff, TYPES } |
| 38 | +export { diff, TYPE } |
0 commit comments