mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-14 14:50:16 +00:00
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
/** Anything mergeable: a plain-ish object, explicitly not an array or another iterable. */
|
|
export type UnknownRecord = Record<string | number, unknown> & {[Symbol.iterator]?: never};
|
|
|
|
function isUnknownRecord(value: unknown): value is UnknownRecord {
|
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function cloneArray(input: readonly unknown[]): unknown[] {
|
|
const len = input.length;
|
|
const output: unknown[] = new Array(len);
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
const val = input[i];
|
|
output[i] = isUnknownRecord(val) ? cloneObject(val) : Array.isArray(val) ? cloneArray(val) : val;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function cloneObject(input: UnknownRecord): UnknownRecord {
|
|
const output: UnknownRecord = {};
|
|
|
|
for (const key of Object.keys(input)) {
|
|
if (key !== "__proto__" && key !== "constructor" && key !== "prototype") {
|
|
const val = input[key];
|
|
output[key] = isUnknownRecord(val) ? cloneObject(val) : Array.isArray(val) ? cloneArray(val) : val;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Merge all sources into `target` recursively.
|
|
*
|
|
* Key behavior:
|
|
* - ignore properties `__proto__`, `constructor` & `prototype`
|
|
* - assumes no infinite circular possible (unhandled for perf)
|
|
*
|
|
* Pass empty object `{}` as `target` to return a new object without modifying any existing objects.
|
|
*/
|
|
export function objectAssignDeep<T extends UnknownRecord, S extends readonly UnknownRecord[]>(
|
|
target: T,
|
|
...sources: S
|
|
): T & UnionToIntersection<S[number]> {
|
|
for (const source of sources) {
|
|
for (const key of Object.keys(source)) {
|
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
continue;
|
|
}
|
|
|
|
const value = source[key];
|
|
|
|
if (isUnknownRecord(value)) {
|
|
const existing = target[key];
|
|
|
|
(target as UnknownRecord)[key] = isUnknownRecord(existing) ? objectAssignDeep({}, existing, value) : cloneObject(value);
|
|
} else if (Array.isArray(value)) {
|
|
(target as UnknownRecord)[key] = cloneArray(value);
|
|
} else {
|
|
(target as UnknownRecord)[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return target as T & UnionToIntersection<S[number]>;
|
|
}
|