oxc-project/oxc · warning · OxcDiagnostic
Use `structuredClone(…)` to create a deep clone.
Error message
Use `structuredClone(…)` to create a deep clone.
What it means
This is the oxlint rule `unicorn/prefer-structured-clone`. It flags common hand-rolled deep clones — most notably `JSON.parse(JSON.stringify(obj))` — and known clone utility calls, recommending the platform's `structuredClone(…)`, which handles cyclic references, Maps, Sets, Dates, and typed arrays that the JSON round-trip silently corrupts. The `functions` config lists clone functions you are allowed to keep using.
Source
Thrown at crates/oxc_linter/src/rules/unicorn/prefer_structured_clone.rs:22
AstKind,
ast::{CallExpression, Expression},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{
AstNode,
ast_util::is_method_call,
context::LintContext,
fixer::{RuleFix, RuleFixer},
rule::{DefaultRuleConfig, Rule},
};
fn prefer_structured_clone_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Use `structuredClone(…)` to create a deep clone.")
.with_help("Switch to `structuredClone(…)`.")
.with_label(span)
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct PreferStructuredClone(Box<PreferStructuredCloneConfig>);
#[derive(Debug, Clone, JsonSchema, Deserialize)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
pub struct PreferStructuredCloneConfig {
/// List of functions that are allowed to be used for deep cloning instead of structuredClone.
functions: Vec<String>,
}
impl Default for PreferStructuredCloneConfig {
fn default() -> Self {
Self { functions: vec!["cloneDeep".to_string(), "utils.clone".to_string()] }
}View on GitHub (pinned to e1e7af627c)
Solutions
- Replace `JSON.parse(JSON.stringify(obj))` with `structuredClone(obj)`.
- For utility calls, switch to `structuredClone` or add your preferred helper to the allow-list: `{ "rules": { "unicorn/prefer-structured-clone": ["error", { "functions": ["cloneDeep"] }] } }`.
- Verify your runtime supports it (Node 17+, Deno, browsers since ~2022); for older targets keep the old code and disable the rule.
- Remember `structuredClone` cannot clone functions, DOM nodes, or property descriptors/prototypes — if you rely on those, keep your custom clone and allow-list it.
Example fix
// before const draft = JSON.parse(JSON.stringify(state)); // after const draft = structuredClone(state);
Defensive patterns
Strategy: validation
Validate before calling
// Prefer the platform API for deep copies
const draft = structuredClone(state);
// If you must keep a helper, allow-list it:
// .oxlintrc.json: "unicorn/prefer-structured-clone": ["error", { "functions": ["cloneDeep"] }] Try / catch
// structuredClone throws DataCloneError on non-cloneable values — catch it at the boundary
try {
const copy = structuredClone(value);
} catch (err) {
if (err instanceof Error && err.name === 'DataCloneError') {
// handle functions/DOM nodes/prototype-carrying objects explicitly
} else throw err;
} Prevention
- Never deep-clone via JSON round-trip: it drops undefined, mangles Date/Map/Set, and throws on cycles.
- Confirm Node 17+/modern browser support before standardizing on structuredClone.
- Allow-list lodash `cloneDeep` via the `functions` config when prototypes or functions must survive the copy.
When it happens
Trigger: A call recognized as a deep-clone idiom: `JSON.parse(JSON.stringify(x))` (matched via `is_method_call` on the JSON pair), or calls to configured clone helpers (e.g. lodash-style `cloneDeep(x)`) that are not in the rule's allow-list. Fixer support (`RuleFix`/`RuleFixer`) can rewrite simple cases to `structuredClone(x)`.
Common situations: State duplication in reducers and cache copies; the classic bug is the JSON round-trip dropping `undefined` values, turning `Date`s into strings, `NaN` into `null`, and throwing on cycles — exactly what `structuredClone` (Node >= 17, modern browsers) fixes. Teams with lodash may add `cloneDeep` to the `functions` allow-list and keep it.
Related errors
- The catch parameter {caught_ident:?} should be named {expect
- Inconsistent assert usage.
- Unnecessary `.getTime()` call
- Prefer consistent types when spreading a ternary in an array
- Prefer `{} {}` over `{} {}` to check {}.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/dd858fa4c408067b.
Report an issue: GitHub.