typescriptutilityobjectsdata
Deep Clone Utility
Deep clone utility that correctly handles Date, RegExp, Map, Set, arrays, and nested objects with circular reference safety.
Code
function deepClone<T>(source: T, seen = new WeakMap()): T {
// Primitives and null
if (source === null || typeof source !== 'object') {
return source;
}
// Circular reference detection
if (seen.has(source as object)) {
return seen.get(source as object);
}
// Date
if (source instanceof Date) {
return new Date(source.getTime()) as T;
}
// RegExp
if (source instanceof RegExp) {
return new RegExp(source.source, source.flags) as T;
}
// Map
if (source instanceof Map) {
const mapClone = new Map();
seen.set(source, mapClone);
source.forEach((value, key) => {
mapClone.set(deepClone(key, seen), deepClone(value, seen));
});
return mapClone as T;
}
// Set
if (source instanceof Set) {
const setClone = new Set();
seen.set(source, setClone);
source.forEach((value) => {
setClone.add(deepClone(value, seen));
});
return setClone as T;
}
// Array
if (Array.isArray(source)) {
const arrClone: unknown[] = [];
seen.set(source, arrClone);
for (const item of source) {
arrClone.push(deepClone(item, seen));
}
return arrClone as T;
}
// Plain objects
const objClone = Object.create(Object.getPrototypeOf(source));
seen.set(source as object, objClone);
for (const key of Reflect.ownKeys(source as object)) {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) {
if ('value' in descriptor) {
objClone[key] = deepClone(descriptor.value, seen);
} else {
Object.defineProperty(objClone, key, descriptor);
}
}
}
return objClone as T;
}
// Usage
const original = {
name: 'config',
created: new Date(),
pattern: /test/gi,
tags: new Set(['a', 'b']),
metadata: new Map([['key', { nested: true }]]),
items: [1, { deep: { value: 42 } }],
};
const cloned = deepClone(original);
// All nested structures are independent copiesVariations
structuredClone with fallback
function safeClone<T>(source: T): T {
// Use native structuredClone when available (modern browsers + Node 17+)
if (typeof structuredClone === 'function') {
try {
return structuredClone(source);
} catch {
// Falls through to manual clone for unsupported types
// (e.g., functions, DOM nodes, symbols in properties)
}
}
// Fallback: JSON round-trip for simple structures
if (isJsonSafe(source)) {
return JSON.parse(JSON.stringify(source));
}
// Full fallback: manual deep clone
return deepClone(source);
}
function isJsonSafe(value: unknown): boolean {
if (value === null || value === undefined) return true;
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') return true;
if (type === 'function' || type === 'symbol') return false;
if (value instanceof Date || value instanceof RegExp) return false;
if (value instanceof Map || value instanceof Set) return false;
if (Array.isArray(value)) return value.every(isJsonSafe);
if (type === 'object') {
return Object.values(value as Record<string, unknown>).every(isJsonSafe);
}
return false;
}
// Usage
const data = { users: [{ name: 'Alice', scores: [10, 20] }] };
const copy = safeClone(data);
// Uses structuredClone if available, falls back gracefully