Skip to content
SP StackPractices
beginner Por Mathias Paulenko

Deep Clone de Objetos en JavaScript: Mas alla de JSON.parse

Compara estrategias de deep clone incluyendo JSON.parse, structuredClone, recursion manual y librerias para copiar objetos anidados con referencias circulares

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Deep Clone de Objetos en JavaScript: Mas alla de JSON.parse

Copia objetos JavaScript anidados sin referencias compartidas usando enfoques modernos y legacy. Esta recipe compara JSON.parse, structuredClone, clonado recursivo manual y soluciones con librerias mientras maneja casos edge como referencias circulares, funciones y tipos especiales de objetos.

Cuando Usar Esto

  • El manejo de estado requiere actualizaciones inmutables sin mutar datos originales
  • Las respuestas de API son cacheadas y no deben ser modificadas por consumidores
  • Los objetos de configuracion se pasan a multiples modulos que pueden modificarlos

Solucion

1. Enfoque JSON.parse (Limitado)

// clones/JsonClone.ts
function jsonClone<T>(obj: T): T {
  return JSON.parse(JSON.stringify(obj));
}

// Funciona para objetos plain y arrays
const original = { a: 1, b: { c: 2 } };
const copy = jsonClone(original);

// Limitaciones
jsonClone({ date: new Date() });        // Date se convierte en string
jsonClone({ map: new Map() });         // Map se convierte en {}
jsonClone({ fn: () => 1 });           // Function se convierte en undefined
jsonClone({ a: {} }); copy.a = original; // Circular: throw

2. structuredClone (Browsers Modernos y Node 17+)

// clones/StructuredClone.ts
function modernClone<T>(obj: T): T {
  return structuredClone(obj);
}

// Soporta mas tipos
const original = {
  date: new Date(),
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  arrayBuffer: new Uint8Array([1, 2, 3]).buffer,
  nested: { a: 1 },
};

const copy = modernClone(original);

// Limitaciones
modernClone({ fn: () => 1 });          // Function throw
modernClone({ el: document.body });   // DOM nodes throw

3. Clonado Recursivo Manual

// clones/RecursiveClone.ts
function deepClone<T>(obj: T, cache = new WeakMap<object, unknown>()): T {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  if (cache.has(obj)) {
    return cache.get(obj) as T;
  }

  if (obj instanceof Date) {
    return new Date(obj.getTime()) as unknown as T;
  }

  if (Array.isArray(obj)) {
    const copy: unknown[] = [];
    cache.set(obj, copy);
    obj.forEach((item, index) => {
      copy[index] = deepClone(item, cache);
    });
    return copy as unknown as T;
  }

  const copy = Object.create(Object.getPrototypeOf(obj));
  cache.set(obj, copy);

  Object.entries(obj as Record<string, unknown>).forEach(([key, value]) => {
    copy[key] = deepClone(value, cache);
  });

  return copy;
}

4. Clonado con Librerias

// clones/LibraryClone.ts
import cloneDeep from 'lodash/cloneDeep';
import { klona } from 'klona';

const lodashCopy = cloneDeep(original);
const klonaCopy = klona(original);

const obj = {
  date: new Date(),
  regex: /test/gi,
  nested: { a: 1 },
};

lodashCopy.nested.a = 2; // obj.nested.a sigue siendo 1
klonaCopy.nested.a = 3;  // obj.nested.a sigue siendo 1

5. Comparacion de Rendimiento

// benchmarks/cloneBench.ts
const largeObject = {
  users: Array.from({ length: 1000 }, (_, i) => ({
    id: i,
    name: `User ${i}`,
    metadata: { created: new Date(), tags: ['a', 'b'] },
  })),
};

// Resultados para 1000 iteraciones (aproximados):
// JSON.parse:      ~50ms  (mas rapido pero limitado)
// structuredClone:  ~80ms (nativo, sin funciones)
// klona:          ~120ms (compacto, moderno)
// lodash:         ~200ms (mas robusto)
// recursive:      ~250ms (customizable)

Como Funciona

  • JSON.parse serializa a string y luego parsea, eliminando tipos no-JSON
  • structuredClone es una API nativa que soporta mas tipos pero excluye funciones
  • Clonado recursivo atraviesa propiedades, preservando prototype chains y manejando refs circulares
  • Librerias optimizan hot paths y manejan casos edge como descriptors y symbols

Consideraciones de Produccion

  • Usa structuredClone en ambientes modernos para rendimiento nativo
  • Prefiere klona sobre lodash si importa el tamano del bundle
  • Para estado de React, considera Immer para structural sharing en lugar de clonado completo

Errores Comunes

  • Usar JSON.parse para objetos que contienen Dates, Maps o funciones
  • Hacer spread de objetos anidados ({ ...obj }) que solo hace shallow-clone del primer nivel
  • No manejar referencias circulares, causando stack overflow en soluciones recursivas

FAQ

P: Es const copy = { ...original } un deep clone? R: No. Crea un shallow copy. Los objetos anidados siguen siendo referencias compartidas.

P: Puedo hacer deep clone de instancias de clases? R: structuredClone elimina metodos. Usa recursion manual o librerias que preservan prototypes.