[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-structuredclone-deep-clone":32,"quiz-article-structuredclone-deep-clone":48},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-323b-7314-a58d-8c3fbb81c12f","structuredclone-deep-clone","PRACTICE_QUIZ","structuredClone vs the JSON hack","Six questions on what structuredClone actually preserves versus JSON.parse(JSON.stringify(obj)) — Dates, Maps, Sets, circular references, class instances, and the transfer option.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},6,null,70,"IMMEDIATE",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":81,"bodyMd":222,"seo":223,"translationGroupId":226,"thread":227,"assessments":229,"translations":232,"quiz":234},"019fe660-0596-7433-9dc2-0fc620be2d0f","You've been deep-cloning objects with a JSON hack. `structuredClone` does it right.","JSON.parse(JSON.stringify(obj)) silently corrupts Dates, Maps, Sets, and undefined values. structuredClone is the native replacement — no library, no import, no surprises.","\u002Fmedia\u002Fcovers\u002Fstructuredclone-deep-clone.png",4,"2026-07-10T09:43:33.966Z",12,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"javascript","JavaScript",[66,68,71,74],{"slug":63,"name":67,"color":40},"Javascript",{"slug":69,"name":70,"color":40},"webdev","Webdev",{"slug":72,"name":73,"color":40},"frontend","Frontend",{"slug":75,"name":76,"color":40},"node","Node",{"assessments":78},1,{"slug":34,"title":80},"structuredClone vs spread vs JSON — deep clone playground",{"blocks":82,"version":78},[83,87,90,94,97,100,106,109,112,116,119,123,126,129,132,135,138,141,144,147,151,154,158,161,165,168,171,174,177,180,183,186,189,192,195,198,201,204,207,210,213],{"id":84,"html":85,"type":86},"b1","\u003Cp>The \u003Ccode>JSON.parse(JSON.stringify(obj))\u003C\u002Fcode> incantation is in almost every codebase. It works often enough that most developers have reached for it without a second thought. No imports, no library, a deep clone in one line — memorable and good enough for a decade.\u003C\u002Fp>","paragraph",{"id":88,"html":89,"type":86},"b2","\u003Cp>&quot;Good enough&quot; is doing a lot of work in that sentence.\u003C\u002Fp>",{"id":91,"html":92,"text":92,"type":93,"level":31},"b3","What JSON serialization actually does to your data","heading",{"id":95,"html":96,"type":86},"b4","\u003Cp>\u003Ccode>JSON.stringify\u003C\u002Fcode> serializes a value to a text string. JSON has a limited type vocabulary: strings, numbers, booleans, null, arrays, and plain objects. Anything outside that set either gets transformed silently or dropped entirely.\u003C\u002Fp>",{"id":98,"html":99,"type":86},"b5","\u003Cp>Here&#39;s what happens when you round-trip a realistic object through JSON:\u003C\u002Fp>",{"id":101,"code":102,"type":103,"language":104,"highlight":105},"b6","const original = {\n  createdAt: new Date('2024-01-15'),\n  config: new Map([['theme', 'dark']]),\n  count: undefined,\n  tags: new Set(['css', 'js']),\n};\n\nconst cloned = JSON.parse(JSON.stringify(original));\n\nconsole.log(typeof cloned.createdAt);   \u002F\u002F \"string\" — not a Date\nconsole.log(cloned.config);             \u002F\u002F {} — not a Map\nconsole.log('count' in cloned);         \u002F\u002F false — undefined is dropped\nconsole.log(cloned.tags);              \u002F\u002F {} — not a Set","code","js",[],{"id":107,"html":108,"type":86},"b7","\u003Cp>The clone exists, but it&#39;s not the same shape as the original. A \u003Ccode>Date\u003C\u002Fcode> became a string — so \u003Ccode>cloned.createdAt.getMonth()\u003C\u002Fcode> throws a \u003Ccode>TypeError\u003C\u002Fcode>. A \u003Ccode>Map\u003C\u002Fcode> became an empty object, stripping away \u003Ccode>.get()\u003C\u002Fcode>, \u003Ccode>.has()\u003C\u002Fcode>, and iteration. Undefined properties disappear entirely, which can quietly break code that checks for key presence. \u003Ccode>Set\u003C\u002Fcode> instances collapse to \u003Ccode>{}\u003C\u002Fcode>.\u003C\u002Fp>",{"id":110,"html":111,"type":86},"b8","\u003Cp>Circular references throw outright: \u003Ccode>JSON.stringify\u003C\u002Fcode> raises \u003Ccode>TypeError: cyclic object value\u003C\u002Fcode> rather than attempting to handle them.\u003C\u002Fp>",{"id":113,"html":114,"text":115,"type":93,"level":31},"b9","\u003Ccode>structuredClone\u003C\u002Fcode> — the built-in that handles all of it","structuredClone — the built-in that handles all of it",{"id":117,"html":118,"type":86},"b10","\u003Cp>\u003Ccode>structuredClone\u003C\u002Fcode> is a global function that deep-clones a value using the \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FStructured_clone_algorithm\">Structured Clone Algorithm\u003C\u002Fa> — the same algorithm the browser uses internally to pass data across web workers, \u003Ccode>postMessage\u003C\u002Fcode>, and IndexedDB. It&#39;s been available since it was designed for exactly this purpose.\u003C\u002Fp>",{"id":120,"code":121,"type":103,"language":104,"highlight":122},"b11","const original = {\n  createdAt: new Date('2024-01-15'),\n  config: new Map([['theme', 'dark']]),\n  count: undefined,\n  tags: new Set(['css', 'js']),\n};\n\nconst cloned = structuredClone(original);\n\nconsole.log(cloned.createdAt instanceof Date);    \u002F\u002F true\nconsole.log(cloned.createdAt.getMonth());        \u002F\u002F 0 (January)\nconsole.log(cloned.config instanceof Map);        \u002F\u002F true\nconsole.log(cloned.config.get('theme'));         \u002F\u002F 'dark'\nconsole.log('count' in cloned);                  \u002F\u002F true — undefined is preserved\nconsole.log(cloned.tags instanceof Set);          \u002F\u002F true\nconsole.log(cloned.tags.has('css'));             \u002F\u002F true",[],{"id":124,"html":125,"type":86},"b12","\u003Cp>No import. No library. The types survive the clone.\u003C\u002Fp>",{"id":127,"html":128,"type":86},"b13","\u003C!-- playground:start -->",{"id":130,"html":131,"text":131,"type":93,"level":31},"b14","🎮 Try it yourself",{"id":133,"html":134,"type":86},"b15","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-10-structuredclone-deep-clone\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":136,"html":137,"type":86},"b16","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":139,"html":140,"type":86},"b17","\u003C!-- playground:end -->",{"id":142,"html":143,"text":143,"type":93,"level":31},"b18","Circular references — handled",{"id":145,"html":146,"type":86},"b19","\u003Cp>The thing that makes \u003Ccode>JSON.parse(JSON.stringify())\u003C\u002Fcode> throw, \u003Ccode>structuredClone\u003C\u002Fcode> handles correctly:\u003C\u002Fp>",{"id":148,"code":149,"type":103,"language":104,"highlight":150},"b20","const a = { name: 'a' };\na.self = a; \u002F\u002F circular reference\n\nconst cloned = structuredClone(a);\nconsole.log(cloned.self === cloned); \u002F\u002F true — the graph structure is preserved\nconsole.log(cloned === a);           \u002F\u002F false — it's a distinct object",[],{"id":152,"html":153,"type":86},"b21","\u003Cp>The algorithm preserves the object graph: shared references inside the original are shared in the clone too. Circular structures don&#39;t cause infinite loops or errors — they produce a correctly-structured clone.\u003C\u002Fp>",{"id":155,"html":156,"text":157,"type":93,"level":31},"b22","The one thing it won&#39;t clone","The one thing it won't clone",{"id":159,"html":160,"type":86},"b23","\u003Cp>\u003Ccode>structuredClone\u003C\u002Fcode> follows the same constraints as \u003Ccode>postMessage\u003C\u002Fcode>. It clones \u003Cem>data\u003C\u002Fem>, not \u003Cem>behavior\u003C\u002Fem>. Functions are not transferable, so they throw:\u003C\u002Fp>",{"id":162,"code":163,"type":103,"language":104,"highlight":164},"b24","structuredClone({ fn: () => {} }); \u002F\u002F TypeError: fn could not be cloned",[],{"id":166,"html":167,"type":86},"b25","\u003Cp>The same applies to DOM nodes and class instances where the prototype chain matters — the data transfers, but the prototype doesn&#39;t, so the clone is a plain object. Error objects, however, \u003Cem>are\u003C\u002Fem> supported and clone correctly.\u003C\u002Fp>",{"id":169,"html":170,"type":86},"b26","\u003Cp>This isn&#39;t a bug. The algorithm is intentionally scoped to values, not objects with identity. For the vast majority of real use cases — API response data, form state, configuration objects, anything you&#39;d pass through \u003Ccode>postMessage\u003C\u002Fcode> — it&#39;s exactly the right scope. If you need to clone class instances and preserve their prototype, a library like \u003Ccode>lodash.cloneDeep\u003C\u002Fcode> covers that case.\u003C\u002Fp>",{"id":172,"html":173,"text":173,"type":93,"level":31},"b27","Browser support",{"id":175,"html":176,"type":86},"b28","\u003Cp>\u003Ccode>structuredClone\u003C\u002Fcode> landed in Chrome 98, Firefox 94, and Safari 15.4. Baseline 2022 — every browser released in the last three years. Node.js added it in version 17.0.\u003C\u002Fp>",{"id":178,"html":179,"type":86},"b29","\u003Cp>No \u003Ccode>typeof structuredClone !== &#39;undefined&#39;\u003C\u002Fcode> guard needed unless you&#39;re targeting something very old. If you&#39;re on a current browser or a current Node, it&#39;s there.\u003C\u002Fp>",{"id":181,"html":182,"type":86},"b30","\u003C!-- quiz:start -->",{"id":184,"html":185,"text":185,"type":93,"level":31},"b31","🧠 Test yourself",{"id":187,"html":188,"type":86},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-10-structuredclone-deep-clone\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":86},"b33","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":86},"b34","\u003C!-- quiz:end -->",{"id":196,"html":197,"text":197,"type":93,"level":31},"b35","What to do now",{"id":199,"html":200,"type":86},"b36","\u003Cp>Search your codebase for \u003Ccode>JSON.parse(JSON.stringify\u003C\u002Fcode>. For every match, ask: does this object ever contain a \u003Ccode>Date\u003C\u002Fcode>, \u003Ccode>Map\u003C\u002Fcode>, \u003Ccode>Set\u003C\u002Fcode>, \u003Ccode>undefined\u003C\u002Fcode> value, or circular reference? If yes, the clone is silently dropping or corrupting data and you should replace it with \u003Ccode>structuredClone\u003C\u002Fcode>.\u003C\u002Fp>",{"id":202,"html":203,"type":86},"b37","\u003Cp>If the object is genuinely just nested strings, numbers, and arrays — the JSON round-trip technically works — \u003Ccode>structuredClone\u003C\u002Fcode> is still clearer about intent, faster in modern engines, and safe when the object shape grows to include a \u003Ccode>Date\u003C\u002Fcode> field six months from now.\u003C\u002Fp>",{"id":205,"html":206,"type":86},"b38","\u003Cp>The JSON hack was the available tool for a decade. The built-in is better in every dimension that matters for data cloning, and it&#39;s been in every major runtime for three years. Time to use the right primitive.\u003C\u002Fp>",{"id":208,"type":209},"b39","divider",{"id":211,"html":212,"type":86},"b40","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":214,"type":215,"items":216,"ordered":18},"b41","list",[217,218,219,220,221],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","The `JSON.parse(JSON.stringify(obj))` incantation is in almost every codebase. It works often enough that most developers have reached for it without a second thought. No imports, no library, a deep clone in one line — memorable and good enough for a decade.\n\n\"Good enough\" is doing a lot of work in that sentence.\n\n## What JSON serialization actually does to your data\n\n`JSON.stringify` serializes a value to a text string. JSON has a limited type vocabulary: strings, numbers, booleans, null, arrays, and plain objects. Anything outside that set either gets transformed silently or dropped entirely.\n\nHere's what happens when you round-trip a realistic object through JSON:\n\n```js\nconst original = {\n  createdAt: new Date('2024-01-15'),\n  config: new Map([['theme', 'dark']]),\n  count: undefined,\n  tags: new Set(['css', 'js']),\n};\n\nconst cloned = JSON.parse(JSON.stringify(original));\n\nconsole.log(typeof cloned.createdAt);   \u002F\u002F \"string\" — not a Date\nconsole.log(cloned.config);             \u002F\u002F {} — not a Map\nconsole.log('count' in cloned);         \u002F\u002F false — undefined is dropped\nconsole.log(cloned.tags);              \u002F\u002F {} — not a Set\n```\n\nThe clone exists, but it's not the same shape as the original. A `Date` became a string — so `cloned.createdAt.getMonth()` throws a `TypeError`. A `Map` became an empty object, stripping away `.get()`, `.has()`, and iteration. Undefined properties disappear entirely, which can quietly break code that checks for key presence. `Set` instances collapse to `{}`.\n\nCircular references throw outright: `JSON.stringify` raises `TypeError: cyclic object value` rather than attempting to handle them.\n\n## `structuredClone` — the built-in that handles all of it\n\n`structuredClone` is a global function that deep-clones a value using the [Structured Clone Algorithm](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FStructured_clone_algorithm) — the same algorithm the browser uses internally to pass data across web workers, `postMessage`, and IndexedDB. It's been available since it was designed for exactly this purpose.\n\n```js\nconst original = {\n  createdAt: new Date('2024-01-15'),\n  config: new Map([['theme', 'dark']]),\n  count: undefined,\n  tags: new Set(['css', 'js']),\n};\n\nconst cloned = structuredClone(original);\n\nconsole.log(cloned.createdAt instanceof Date);    \u002F\u002F true\nconsole.log(cloned.createdAt.getMonth());        \u002F\u002F 0 (January)\nconsole.log(cloned.config instanceof Map);        \u002F\u002F true\nconsole.log(cloned.config.get('theme'));         \u002F\u002F 'dark'\nconsole.log('count' in cloned);                  \u002F\u002F true — undefined is preserved\nconsole.log(cloned.tags instanceof Set);          \u002F\u002F true\nconsole.log(cloned.tags.has('css'));             \u002F\u002F true\n```\n\nNo import. No library. The types survive the clone.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-10-structuredclone-deep-clone\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Circular references — handled\n\nThe thing that makes `JSON.parse(JSON.stringify())` throw, `structuredClone` handles correctly:\n\n```js\nconst a = { name: 'a' };\na.self = a; \u002F\u002F circular reference\n\nconst cloned = structuredClone(a);\nconsole.log(cloned.self === cloned); \u002F\u002F true — the graph structure is preserved\nconsole.log(cloned === a);           \u002F\u002F false — it's a distinct object\n```\n\nThe algorithm preserves the object graph: shared references inside the original are shared in the clone too. Circular structures don't cause infinite loops or errors — they produce a correctly-structured clone.\n\n## The one thing it won't clone\n\n`structuredClone` follows the same constraints as `postMessage`. It clones *data*, not *behavior*. Functions are not transferable, so they throw:\n\n```js\nstructuredClone({ fn: () => {} }); \u002F\u002F TypeError: fn could not be cloned\n```\n\nThe same applies to DOM nodes and class instances where the prototype chain matters — the data transfers, but the prototype doesn't, so the clone is a plain object. Error objects, however, *are* supported and clone correctly.\n\nThis isn't a bug. The algorithm is intentionally scoped to values, not objects with identity. For the vast majority of real use cases — API response data, form state, configuration objects, anything you'd pass through `postMessage` — it's exactly the right scope. If you need to clone class instances and preserve their prototype, a library like `lodash.cloneDeep` covers that case.\n\n## Browser support\n\n`structuredClone` landed in Chrome 98, Firefox 94, and Safari 15.4. Baseline 2022 — every browser released in the last three years. Node.js added it in version 17.0.\n\nNo `typeof structuredClone !== 'undefined'` guard needed unless you're targeting something very old. If you're on a current browser or a current Node, it's there.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-10-structuredclone-deep-clone)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## What to do now\n\nSearch your codebase for `JSON.parse(JSON.stringify`. For every match, ask: does this object ever contain a `Date`, `Map`, `Set`, `undefined` value, or circular reference? If yes, the clone is silently dropping or corrupting data and you should replace it with `structuredClone`.\n\nIf the object is genuinely just nested strings, numbers, and arrays — the JSON round-trip technically works — `structuredClone` is still clearer about intent, faster in modern engines, and safe when the object shape grows to include a `Date` field six months from now.\n\nThe JSON hack was the available tool for a decade. The built-in is better in every dimension that matters for data cloning, and it's been in every major runtime for three years. Time to use the right primitive.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":224,"canonical":225,"description":51},"You've been deep-cloning objects with a JSON hack. `structuredClone` d","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstructuredclone-deep-clone","019fe660-0596-7433-9dc2-12f3abecaf83",{"id":228,"locked":18},"019fe660-0c9c-740b-8e84-399525d22f2d",[230],{"id":33,"slug":34,"title":36,"_count":231},{"questions":39},[233],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":235,"questionCount":39},{"questions":39}]