-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
112 lines (99 loc) · 3.17 KB
/
index.js
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
110
111
112
var get = require("lodash.get");
/**
* Check if a passed variable is an object.
* @param {*} x - Anything you want to check.
*/
const isObject = x => typeof x === "object" && x !== null;
/**
* Check if variable is undefined.
* @param {*} x - Anything you want to check.
*/
const isUndefined = x => typeof x === "undefined";
/**
* Just a shorthand for the native isArray method.
* @param {*} x - Anything you want to check.
*/
const isArray = x => Array.isArray(x);
/**
* A function generator that returs a function that
* keep list of seen objects and returns a special
* string when it encouters an already seen object.
*/
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (isObject(value)) {
if (seen.has(value)) {
return "[Cyclic]";
}
seen.add(value);
}
return value;
};
};
/**
* Merges two objects to a single object. Values for clashing
* paths are bundeled to arrays in order of appearance.
* @param {object} a - A base object.
* @param {object} b - An object to merge with the base object.
*/
const joinObjects = (a, b) => {
const path = [];
const recursivelyJoinObjects = (a, b) => {
const base = JSON.parse(JSON.stringify(a, getCircularReplacer()));
const toMerge = JSON.parse(JSON.stringify(b, getCircularReplacer()));
const localPath = [];
for (let key in toMerge) {
path.push(key);
localPath.push(key);
const currentValue = get(base, localPath);
const newValue = toMerge[key];
if (isUndefined(currentValue) && isUndefined(newValue)) {
base[key] = undefined;
} else if (!isUndefined(currentValue) && isUndefined(newValue)) {
base[key] = currentValue;
} else if (isUndefined(currentValue) && !isUndefined(newValue)) {
base[key] = newValue;
} else if (!isUndefined(currentValue) && !isUndefined(newValue)) {
if (isObject(currentValue) && isObject(newValue)) {
if (isArray(currentValue) && isArray(newValue)) {
base[key] = currentValue.concat(newValue);
} else if (isArray(currentValue)) {
currentValue.push(newValue);
base[key] = currentValue;
} else {
base[key] = recursivelyJoinObjects(currentValue, newValue);
}
} else {
if (isArray(currentValue)) {
currentValue.push(newValue);
base[key] = currentValue;
} else {
base[key] = [currentValue, newValue];
}
}
}
localPath.pop();
path.pop();
}
return base;
};
return recursivelyJoinObjects(a, b);
};
/**
* Merges all passed objects to a single object. Values for
* clashing paths are bundeled to arrays in order of appearance.
* @param {object} objectArray - All objects to be merged as arguments.
*/
module.exports = (...objectArray) => {
if (objectArray.length === 0)
throw new Error(
"You'll need to provide at least two object to merge them."
);
else if (objectArray.length === 1) return objectArray[0];
let result = objectArray.shift();
objectArray.forEach(object => {
result = joinObjects(result, object);
});
return result;
};