structuredClone al posto di JSON.parse(JSON.stringify(x))

Per anni il modo più veloce per fare una copia profonda di un oggetto in JavaScript è stato:
const copia = JSON.parse(JSON.stringify(originale));
Funziona, ma è un trucco che sfrutta un side-effect di due funzioni pensate per altro. E i side-effect si pagano: alcuni tipi di dato passano indenni per la serializzazione JSON, altri escono rotti senza nessun errore. Dal 2022 c’è un modo nativo per farlo bene: structuredClone().
Cosa rompe il vecchio trucco
JSON.stringify sa rappresentare solo quello che il formato JSON prevede: oggetti, array, stringhe, numeri, booleani, null. Tutto il resto viene silenziosamente trasformato o perso.
const originale = {
quando: new Date(),
mappa: new Map([["a", 1]]),
insieme: new Set([1, 2, 3]),
indefinito: undefined,
numeroGrande: 10n,
};
const copia = JSON.parse(JSON.stringify(originale));
console.log(copia);
// {
// quando: "2026-09-06T10:00:00.000Z", <- diventata una stringa, non più un Date
// mappa: {}, <- Map svuotata: JSON non sa serializzarla
// insieme: {}, <- stessa sorte per Set
// indefinito viene rimosso del tutto
// numeroGrande: lancia TypeError, JSON non sa gestire i BigInt
// }
Il caso più insidioso è Date: il codice non si rompe, continua a girare, ma copia.quando non è più un oggetto Date. Se più avanti chiami copia.quando.getMonth(), l’errore arriva lontano dal punto in cui la copia è stata fatta, e ci vuole tempo a risalire alla causa.
C’è poi un caso che il trucco JSON non gestisce proprio: i riferimenti circolari.
const nodo = { valore: 1 };
nodo.self = nodo;
JSON.stringify(nodo); // TypeError: Converting circular structure to JSON
Cosa fa structuredClone
structuredClone() è una funzione globale, disponibile nei browser moderni e in Node.js dalla versione 17. Usa lo stesso algoritmo di serializzazione con cui il browser passa dati tra postMessage, worker e IndexedDB, quindi conosce molti più tipi:
const originale = {
quando: new Date(),
mappa: new Map([["a", 1]]),
insieme: new Set([1, 2, 3]),
};
const copia = structuredClone(originale);
copia.quando instanceof Date; // true
copia.mappa instanceof Map; // true
copia.insieme instanceof Set; // true
E gestisce anche i cicli, senza bisogno di librerie:
const nodo = { valore: 1 };
nodo.self = nodo;
const copia = structuredClone(nodo);
copia.self === copia; // true, il ciclo è stato ricreato correttamente
È anche più veloce: non passa da una rappresentazione testuale intermedia, lavora direttamente sulla struttura in memoria.
Cosa non copia
structuredClone() non è magia, e i suoi limiti sono precisi:
- Funzioni: non sono clonabili. Se un oggetto ha un metodo o una callback tra le sue proprietà,
structuredClone()lanciaDataCloneError. - Prototipi: la copia è sempre un oggetto semplice (
Object.prototype), le istanze di classi personalizzate perdono i metodi. Symbol: le proprietà con chiaveSymbolvengono ignorate.- DOM nodes: non clonabili (per quello c’è
Node.cloneNode()).
class Prodotto {
constructor(nome) { this.nome = nome; }
saluta() { return `Ciao, sono ${this.nome}`; }
}
const p = new Prodotto("tavolo");
const copia = structuredClone(p);
copia.nome; // "tavolo", il dato c'è
copia.saluta; // undefined, il metodo no
Se serve preservare il prototipo, o clonare oggetti con funzioni al loro interno, structuredClone() non basta: lì restano utili librerie come lodash.cloneDeep, pensate apposta per quel caso.
Quando serve ancora una libreria
Per il caso comune — copiare uno stato, un oggetto di configurazione, dati arrivati da un’API prima di modificarli — structuredClone() è la scelta giusta: nativo, corretto sui tipi che JSON rompe, senza dipendenze. Se invece nell’oggetto ci sono istanze di classi di cui contano i metodi, o funzioni da preservare, va usata una libreria dedicata: non è un problema che una copia strutturale possa risolvere.
