You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
44 lines
1.9 KiB
JavaScript
44 lines
1.9 KiB
JavaScript
"use strict";
|
|
|
|
// A strict deep-merging implementation that *only* merges regular objects, and prevents prototype pollution, and also optionally allows specifying a value mapper (to avoid the need for a second traversal)
|
|
// FIXME: Publish this as a stand-alone package?
|
|
|
|
function isObject(value) {
|
|
// TODO: Disallow special object types, for statically defined values (or just disallow specifying static values in the root schema in dlayer?)
|
|
// FIXME: The __moduleID is a hack to prevent it from recognizing a module-association wrapper as a schema object; should find a better way to do this, that generalizes to a stand-alone package
|
|
return (value != null && typeof value === "object" && !Array.isArray(value) && value.__moduleID == null);
|
|
}
|
|
|
|
module.exports = function deepMergeAndMap(a, b, valueMapper = (value) => value) {
|
|
let merged = Object.create(null);
|
|
let keys = new Set([ ... Object.keys(a), ... Object.keys(b) ]);
|
|
|
|
for (let key of keys) {
|
|
// Technically over-blocks *any* 'constructor' key
|
|
if (key === "__proto__" || key === "constructor") {
|
|
continue;
|
|
}
|
|
|
|
let valueA = a[key];
|
|
let valueB = b[key];
|
|
|
|
if (isObject(valueA) && valueB === undefined) {
|
|
merged[key] = valueA;
|
|
} else if (isObject(valueB) && valueA === undefined) {
|
|
// This looks hacky, but it ensures that the behaviour (eg. value mapping) is consistent between initially-created subtrees and those that are merged in later
|
|
merged[key] = deepMergeAndMap({}, valueB, valueMapper);
|
|
} else if (isObject(valueA) && isObject(valueB)) {
|
|
merged[key] = deepMergeAndMap(valueA, valueB, valueMapper);
|
|
} else if (!isObject(valueA) && !isObject(valueB)) {
|
|
merged[key] = (valueB != null)
|
|
? valueMapper(valueB)
|
|
: valueA;
|
|
} else {
|
|
// FIXME: Identifiable error type, and include the error path as well
|
|
throw new Error("Cannot merge non-object into object");
|
|
}
|
|
}
|
|
|
|
return merged;
|
|
};
|