Two genuinely common data-handling needs in Angular — copying an object without it still referencing the original, and parsing a JSON string into a usable object — come up constantly enough to be worth a dedicated reference.
The problem with a shallow copy
const original = { name: 'Alex', address: { city: 'Austin' } };
const copy = { ...original };
copy.address.city = 'Dallas';
console.log(original.address.city); // "Dallas" — the original was mutated too!
The spread operator ({ ...original }) only copies the object's top-level properties — any nested object or array inside it is still the exact same reference shared between the original and the copy, which is precisely how this kind of subtle, hard-to-trace mutation bug happens.
A true deep copy using structuredClone()
const original = { name: 'Alex', address: { city: 'Austin' } };
const copy = structuredClone(original);
copy.address.city = 'Dallas';
console.log(original.address.city); // "Austin" — unaffected
structuredClone(), a native browser API, is the modern, recommended way to deep-copy a plain object or array — it correctly handles nested structures, though it does not work for values it can't structurally clone, like functions or class instances with methods.
The older JSON-based deep copy trick, for comparison
const copy = JSON.parse(JSON.stringify(original));
This was the common workaround before structuredClone() existed — it works for plain data but silently loses anything JSON can't represent (functions, undefined values, Date objects become plain strings) — structuredClone() is the more correct and more modern choice where browser support allows it.
Deep-copying an array of objects
const products = [{ name: 'Mouse', tags: ['electronics'] }, { name: 'Keyboard', tags: ['electronics'] }];
const copiedProducts = structuredClone(products);
Parsing a JSON string into an object
const jsonString = '{"name": "Alex", "age": 30}';
const parsed = JSON.parse(jsonString);
console.log(parsed.name); // "Alex"
Handling a malformed JSON string safely
function safeJsonParse(jsonString: string): T | null {
try {
return JSON.parse(jsonString) as T;
} catch {
return null;
}
}
JSON.parse() throws an exception on invalid JSON rather than returning a fallback value — wrapping it in a try/catch (as shown) is necessary any time the input string's validity isn't already guaranteed, such as data read from local storage or an external, less-trusted source.
Converting an object back to a JSON string
const jsonString = JSON.stringify(parsed);
const prettyJsonString = JSON.stringify(parsed, null, 2); // indented, human-readable
The third argument to JSON.stringify() (2 here) adds indentation for human-readable output — genuinely useful for debugging or displaying formatted JSON in a UI, though it's unnecessary overhead for JSON meant only to be sent over the network or stored compactly.