deepClone + deepMerge for nested records
TypeScript
export function deepClone<T>(value: T): T {
if (value === null || typeof value !== 'object') return value;
if (value instanceof Date) return new Date(value.getTime()) as unknown as T;
if (value instanceof Map) {
const copy = new Map<unknown, unknown>();
for (const [key, entry] of value) copy.set(deepClone(key), deepClone(entry));
return copy as unknown as T;
}
if (value instanceof Set) {
const copy = new Set<unknown>();
for (const entry of value) copy.add(deepClone(entry));
return copy as unknown as T;
}
if (Array.isArray(value)) return value.map((entry) => deepClone(entry)) as unknown as T;
const copy: Record<string, unknown> = {};
for (const [key, entry] of Object.entries(value as unknown as Record<string, unknown>)) copy[key] = deepClone(entry);
return copy as unknown as T;
}
/** Recursively merge a patch over a deep clone of the base; arrays are replaced. */
export function deepMerge<T extends Record<string, unknown>>(base: T, patch: Partial<T>): T {
const merged = deepClone(base) as Record<string, unknown>;
for (const [key, value] of Object.entries(patch as unknown as Record<string, unknown>)) {
const current = merged[key];
if (isPlainObject(current) && isPlainObject(value)) merged[key] = deepMerge(current, value);
else if (value !== undefined) merged[key] = deepClone(value);
}
return merged as unknown as T;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype;
}