Deep Clone Objects in TypeScript: Methods & Limits
This guide explains how to create true deep clones of objects in TypeScript, covering the built-in structuredClone, JSON methods, and manual recursion. You’ll learn the strengths and limitations of each approach, so you can choose the right one for your data. No special libraries are required, but a basic understanding of TypeScript types and object references is helpful.
What You’ll Need
- TypeScript 3.7 or later (for the
unknowntype and recursive types). - A modern JavaScript runtime (Node.js 17+ or a current browser) if you plan to use
structuredClone. - An editor or IDE with TypeScript support.
Step-by-Step
- Understand the problem. In JavaScript, objects are stored by reference. When you write
const copy = original, both variables point to the same object. A deep clone creates an entirely new object with the same structure and values, so changes to the clone do not affect the original. - Start with the simplest built-in:
structuredClone. This is the modern, recommended way to deep clone most objects. It handles dates, Maps, Sets, typed arrays, and even circular references.const cloned = structuredClone(original);This works in Node 17+ and all modern browsers. It’s fast and reliable for most use cases.
- Use JSON methods for plain data. If your object contains only JSON-safe values (strings, numbers, booleans, null, arrays, and plain objects), you can use:
const cloned = JSON.parse(JSON.stringify(original));This is a common pattern, but it has significant limitations: it drops functions,
undefined,Symbol, andNaN(converted tonull), and it cannot handle Dates (converted to strings), Maps, Sets, or circular references. - Write a generic recursive clone for custom control. For full control over what gets cloned and how, implement your own function. Here’s a TypeScript version that handles common types:
function deepClone<T>(value: T, seen = new Map<any, any>()): T { if (value === null || typeof value !== 'object') return value; if (seen.has(value)) return seen.get(value); if (value instanceof Date) return new Date(value.getTime()) as any; if (value instanceof RegExp) return new RegExp(value.source, value.flags) as any; if (Array.isArray(value)) { const arr: any[] = []; seen.set(value, arr); value.forEach((item, i) => { arr[i] = deepClone(item, seen); }); return arr as any; } const clonedObj: any = {}; seen.set(value, clonedObj); for (const key of Object.keys(value)) { clonedObj[key] = deepClone((value as any)[key], seen); } return clonedObj as T; }This function handles circular references by tracking visited objects in a
Map. - Add type safety with generics. The function above uses
<T>to preserve the input type. When calling it, TypeScript will infer the return type correctly:const cloned: MyType = deepClone(original);. - Test with your data. Always test the clone with your actual data structure. Verify that nested objects are truly independent by modifying a nested property in the clone and checking the original.
- Consider libraries for complex cases. If you need to clone class instances with methods, or handle exotic types like
Buffer,Mapwith non-string keys, orWeakMap, a library like Lodash’scloneDeepmight be more robust. However, for most applications,structuredCloneis sufficient.
Common Mistakes
- Using the spread operator or
Object.assignfor deep cloning. These only create shallow copies. Nested objects are still shared by reference. - Assuming JSON methods handle all types. They silently corrupt or drop important data like
undefined, functions, and Dates. - Forgetting about circular references. If your object has a property that points back to itself, a naive recursive clone will cause a stack overflow. Use
structuredCloneor a seen-map approach. - Cloning class instances incorrectly.
structuredClonewill clone the instance but lose the prototype chain, turning it into a plain object. Your custom function may need to handle special cases. - Not handling special objects like
MapandSet. JSON methods convert them to empty objects, and a simple recursive clone will create an empty object instead of a Map/Set.
Tips
- Prefer
structuredClonewhenever possible. It’s the most reliable and performant built-in option. - For JSON-safe data, JSON methods are fine and simple. Just be aware of the limitations.
- Write your own clone only when you need specific behavior, such as preserving class instances or handling custom types.
- Use TypeScript’s
unknowntype carefully in recursive functions to avoid type errors. Casting toanyis often necessary. - Consider using a library like Lodash if you need a battle-tested solution and don’t mind the dependency.
Troubleshooting
- If
structuredCloneis not defined: Your runtime may be too old. Check Node version (needs 17+) or use a polyfill likecore-js. - If your clone loses functions or undefined values: You are likely using JSON methods. Switch to
structuredCloneor your custom function. - If you get a stack overflow error: Your object has circular references. Use
structuredCloneor add a seen-map to your recursive function. - If the cloned object’s prototype is wrong:
structuredClonedoesn’t preserve prototypes. You may need to manually set the prototype after cloning or use a library. - If TypeScript complains about types: Ensure your generic function is properly typed. Use
as anyfor internal casting where needed.
Frequently Asked Questions
Does structuredClone work in all browsers?
It works in all modern browsers (Chrome 98+, Firefox 94+, Safari 15.4+). For older browsers, you may need a polyfill or fallback.
Can I deep clone a class instance with methods?
Yes, but you need to handle it manually. structuredClone will clone the instance but lose the prototype. You can set the prototype after cloning, or use a library like Lodash that preserves it.
What is the difference between shallow and deep clone?
A shallow clone copies the top-level properties, but nested objects are still shared by reference. A deep clone recursively copies all nested objects, creating a fully independent copy.
Is JSON.parse(JSON.stringify(obj)) safe for all objects?
No. It only works for JSON-safe data. It will drop functions, undefined, Symbol, and convert Dates to strings, NaN to null, and ignore Map/Set.
How do I handle circular references in a custom clone function?
Use a Map to track already-cloned objects. Before cloning an object, check if it’s in the map; if so, return the stored clone. This breaks the infinite recursion.
Are there performance differences between the methods?
structuredClone is generally the fastest for large objects. JSON methods are fast but limited. Custom recursion is slower but offers flexibility. For most use cases, the performance difference is negligible.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.