froebel
Version:
TypeScript utility library
73 lines (60 loc) • 2.1 kB
JavaScript
import clone from "./clone.mjs";
const merge_ = (a, b) => {
const visited = new Map();
return mergeItems(a, b, visited);
};
const mergeItems = (a, b, visited) => {
if (isPrimitive(a) || isPrimitive(b)) return clone(b);
if (!visited.has(a)) visited.set(a, new Map());
if (visited.get(a).has(b)) return visited.get(a).get(b);
let result;
if (Array.isArray(b)) {
const clonedA = Array.isArray(a) ? clone(a) : [];
const clonedB = clone(b);
result = [...clonedA, ...clonedB];
for (let i = 0; i < result.length; i++) {
if (result[i] === clonedA || result[i] === clonedB) result[i] = result;
}
} else if (b instanceof Set) {
result = new Set([...(a instanceof Set ? a : []), ...b]);
if (result.has(a) || result.has(b)) {
result.delete(a);
result.delete(b);
result.add(result);
}
} else if (b instanceof Map) {
result = new Map([...(a instanceof Map ? a : []), ...b]);
const keys = [...result.keys()];
if (keys.includes(a)) {
result.set(result, result.get(a));
result.delete(a);
}
if (keys.includes(b)) {
result.set(result, result.get(b));
result.delete(b);
}
for (const [k, v] of result) {
if (v === a || v === b) result.set(k, result);
}
} else {
const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])];
result = {};
visited.get(a).set(b, result);
for (const key of keys) {
result[key] = !(key in b) ? clone(a[key]) : !(key in a) ? clone(b[key]) : mergeItems(a[key], b[key], visited);
}
}
visited.get(a).set(b, result);
return result;
};
/**
* Recursively merges `A` and `B`. If a property in `A` and `B` is of a
* different type (i.e. it's not an array, Set, Map, or plain object in both,
* the value from `B` will be used in the result).
*
* If there are self-references in the cloned values, array / Set items, or Map
* keys or values, they will also be self-referencing in the result.
*/
const merge = merge_;
export default merge;
const isPrimitive = value => typeof value !== "object" || value === null;