← Back to snippets
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 copies

Variations

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