import { assert } from "tsafe/assert"; import { is } from "tsafe/is"; function deepClone(src: T): T { const generateId = (() => { const prefix = "xIfKdLsIIdJdLdOeJqePe"; let counter = 0; return () => `${prefix}${counter++}`; })(); const map = new Map(); return JSON.parse( JSON.stringify(src, (...[, value]) => { if (typeof value === "function") { const id = generateId(); map.set(id, value); return id; } return value; }), (...[, value]) => (typeof value === "string" && map.has(value) ? map.get(value) : value), ); } //Warning: Be mindful that because of array this is not idempotent. export function deepAssign(params: { target: Record; source: Record }) { const { target } = params; const source = deepClone(params.source); Object.keys(source).forEach(key => { var dereferencedSource = source[key]; if (target[key] === undefined || !(dereferencedSource instanceof Object)) { Object.defineProperty(target, key, { "enumerable": true, "writable": true, "configurable": true, "value": dereferencedSource, }); return; } const dereferencedTarget = target[key]; if (dereferencedSource instanceof Array) { assert(is(dereferencedTarget)); assert(is(dereferencedSource)); dereferencedSource.forEach(entry => dereferencedTarget.push(entry)); return; } assert(is>(dereferencedTarget)); assert(is>(dereferencedSource)); deepAssign({ "target": dereferencedTarget, "source": dereferencedSource, }); }); }