-
Notifications
You must be signed in to change notification settings - Fork 0
/
json_merge.ts
109 lines (97 loc) · 3.88 KB
/
json_merge.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
export class Conflict {
type: string;
path: string;
base_value: object;
their_value: object;
your_value: object;
constructor(type: string, path: string, base_value: any, their_value: any, your_value: any) {
this.type = type;
this.path = path;
this.base_value = base_value;
this.their_value = their_value;
this.your_value = your_value;
}
}
export class MergeResults {
conflicts: Conflict[];
merged: object;
/**
*
*/
constructor(merged: object, conflicts: Conflict[]) {
this.conflicts = conflicts;
this.merged = merged;
}
}
function get_variable_type(obj: any): string{
if (Array.isArray(obj)) return 'array';
if (typeof(obj) === 'object') return 'object';
return 'primitive';
}
export function json_merge(base: any, theirs: any, yours: any): MergeResults{
const base_type = get_variable_type(base);
const theirs_type = get_variable_type(base);
const yours_type = get_variable_type(base);
if (base_type === theirs_type && theirs_type === yours_type && yours_type === 'object')
return json_merge_objects(<object>base, <object>theirs, <object>yours);
return new MergeResults({}, []);
}
// TODO: support non-objects (arrays, strings, etc)
function json_merge_objects(base: object, theirs: object, yours: object): MergeResults {
const base_keys = new Set(Object.keys(base));
const their_keys = new Set(Object.keys(theirs));
const your_keys = new Set(Object.keys(yours));
const their_added_keys = new Set([...their_keys].filter(key => !base_keys.has(key)));
const your_added_keys = new Set([...your_keys].filter(key => !base_keys.has(key)));
const their_changed_keys = new Set([...their_keys].filter(key => base_keys.has(key) && base[key] != theirs[key]));
const your_changed_keys = new Set([...your_keys].filter(key => base_keys.has(key) && base[key] != yours[key]));
const their_removed_keys = new Set([...base_keys].filter(key => !their_keys.has(key)));
const your_removed_keys = new Set([...base_keys].filter(key => !your_keys.has(key)));
const merge_result = {};
const conflicts = [];
base_keys.forEach(key => {
if (your_removed_keys.has(key)){
if (their_changed_keys.has(key)){
const conflict = new Conflict('change_removed', key, base[key], theirs[key], null);
conflicts.push(conflict);
merge_result[key] = conflict;
}
return;
}
if (their_removed_keys.has(key)){
if (your_changed_keys.has(key)){
const conflict = new Conflict('change_removed', key, base[key], null, yours[key]);
conflicts.push(conflict);
merge_result[key] = conflict;
}
return;
}
if (your_changed_keys.has(key) && their_changed_keys.has(key) && yours[key] != theirs[key]){
const conflict = new Conflict('concurrent_change', key, base[key], theirs[key], yours[key]);
conflicts.push(conflict);
merge_result[key] = conflict;
}
else if (your_changed_keys.has(key))
merge_result[key] = yours[key];
else if (their_changed_keys.has(key))
merge_result[key] = theirs[key];
else
// Can this happen?
merge_result[key] = base[key];
});
their_added_keys.forEach(key => {
if (your_added_keys.has(key) && yours[key] != theirs[key]){
const conflict = new Conflict('concurrent_addition', key, null, theirs[key], yours[key]);
conflicts.push(conflict);
merge_result[key] = conflict;
return;
}
merge_result[key] = theirs[key];
});
your_added_keys.forEach(key => {
if (their_added_keys.has(key))
return;
merge_result[key] = yours[key];
});
return new MergeResults(merge_result, conflicts);
}