Skip to content

Commit 4b01d21

Browse files
committed
feat: utility recursive method to aggregate diffs with rollup in merged tree
1 parent e52f68b commit 4b01d21

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

src/core/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ClassifyRule } from '../types'
22

33
export const DIFF_META_KEY = Symbol('$diff')
4+
export const DIFFS_AGGREGATED_META_KEY = Symbol('$diffs-aggregated')
45
export const DEFAULT_NORMALIZED_RESULT = false
56
export const DEFAULT_OPTION_DEFAULTS_META_KEY = Symbol('$defaults')
67
export const DEFAULT_OPTION_ORIGINS_META_KEY = Symbol('$origins')

src/utils.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,74 @@ export function difference(array1: string[], array2: string[]): string[] {
235235
export function removeSlashes(input: string): string {
236236
return input.replace(/\//g, '')
237237
}
238+
239+
/**
240+
* Traverses the merged document starting from given obj to the bottom and aggregates the diffs with rollup from the bottom up.
241+
* Each object in the tree will have aggregatedDiffProperty only if there are diffs in the object or in the children,
242+
* otherwise the aggregatedDiffProperty is not added.
243+
* Note, that adding/removing the object itself is not included in the aggregation for this object,
244+
* you need retrieve this diffs from parent object if you need them.
245+
* Supports cycled JSO, nested objects and arrays.
246+
* @param obj - The object to aggregate the diffs of.
247+
* @param diffProperty - The property of the object to aggregate the diffs of.
248+
* @param aggregatedDiffProperty - The property of the object to store the aggregated diffs in.
249+
* @returns The aggregated diffs of the given object.
250+
*/
251+
252+
// TODO: generalize to other use cases (like collecting deprecated)
253+
export function aggregateDiffsWithRollup(obj: any, diffProperty: any, aggregatedDiffProperty: any): Set<Diff> | undefined {
254+
255+
const visited = new Set<any>()
256+
257+
function _aggregateDiffsWithRollup(obj: any): Set<Diff> | undefined {
258+
if (obj === null || typeof obj !== 'object' ) {
259+
return undefined
260+
}
261+
262+
if (visited.has(obj)) {
263+
return obj[aggregatedDiffProperty]
264+
}
265+
266+
visited.add(obj)
267+
268+
// Process all children and collect their diffs
269+
const childrenDiffs = new Array<Set<Diff>>()
270+
if (Array.isArray(obj)) {
271+
for (const item of obj) {
272+
const childDiffs = _aggregateDiffsWithRollup(item)
273+
childDiffs && childDiffs.size > 0 && childrenDiffs.push(childDiffs)
274+
}
275+
} else {
276+
for (const [_, value] of Object.entries(obj)) {
277+
const childDiffs = _aggregateDiffsWithRollup(value)
278+
childDiffs && childDiffs.size > 0 && childrenDiffs.push(childDiffs)
279+
}
280+
}
281+
282+
const hasOwnDiffs = diffProperty in obj
283+
284+
if (hasOwnDiffs || childrenDiffs.length > 1) {
285+
// obj aggregated diffs are different from children diffs
286+
const aggregatedDiffs = new Set<Diff>()
287+
for (const childDiffs of childrenDiffs) {
288+
childDiffs.forEach(diff => aggregatedDiffs.add(diff))
289+
}
290+
const diffs = obj[diffProperty]
291+
for (const key in diffs) {
292+
aggregatedDiffs.add(diffs[key])
293+
}
294+
// Store the aggregated diffs in the object
295+
obj[aggregatedDiffProperty] = aggregatedDiffs
296+
}else if (childrenDiffs.length === 1) {
297+
// could reuse a child diffs if there is only one
298+
[obj[aggregatedDiffProperty]] = childrenDiffs
299+
}else{
300+
// no diffs- no aggregated diffs get assigned
301+
}
302+
303+
return obj[aggregatedDiffProperty]
304+
}
305+
306+
return _aggregateDiffsWithRollup(obj)
307+
}
308+

0 commit comments

Comments
 (0)