How to Work with JSON in JavaScript
JSON (JavaScript Object Notation) is the universal data exchange format of the web. Every time your application communicates with an API, stores configuration, saves user preferences, or sends data between a browser and a server, JSON is almost certainly the format carrying that data. Despite its name containing "JavaScript," JSON is language-independent and supported by virtually every programming language and platform.
JSON's strength lies in its simplicity: it is a text-based format that is both human-readable and machine-parsable. JavaScript provides two built-in methods, JSON.stringify() and JSON.parse(), that handle the conversion between JavaScript values and JSON strings. While these methods are straightforward for basic use, they offer powerful customization options (replacers, revivers, custom serialization) and have important limitations that every developer should understand.
This guide covers JSON's syntax rules, both conversion methods with their advanced features, the types JSON cannot represent, custom serialization hooks, and the practical question of using JSON for deep cloning versus modern alternatives.
What Is JSON? (Syntax Rules, Valid Types)
JSON is a text format for representing structured data. It was derived from JavaScript object literal syntax but has stricter rules.
A JSON Example
{
"name": "Alice",
"age": 30,
"isActive": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"hobbies": ["reading", "coding", "hiking"],
"spouse": null
}
JSON Syntax Rules
JSON is more restrictive than JavaScript object literals. Breaking any rule makes the JSON invalid.
1. Property names must be double-quoted strings:
// ✅ Valid JSON
{ "name": "Alice" }
// ❌ Invalid: single quotes
{ 'name': 'Alice' }
// ❌ Invalid: unquoted key
{ name: "Alice" }
2. String values must use double quotes:
// ✅ Valid
{ "greeting": "hello" }
// ❌ Invalid: single quotes
{ "greeting": 'hello' }
// ❌ Invalid: backticks
{ "greeting": `hello` }
3. No trailing commas:
// ✅ Valid
{ "a": 1, "b": 2 }
// ❌ Invalid: trailing comma
{ "a": 1, "b": 2, }
// ❌ Invalid: trailing comma in array
[1, 2, 3,]
4. No comments:
// ❌ Invalid: no comments allowed in JSON
{
// This is not allowed
"name": "Alice" /* neither is this */
}
5. No undefined, functions, or special values:
JSON supports only a specific set of value types.
Valid JSON Types
| Type | JSON Representation | Example |
|---|---|---|
| String | Double-quoted | "hello" |
| Number | Integer or float | 42, 3.14, -7, 1e10 |
| Boolean | true or false | true |
| Null | null | null |
| Object | {} with key-value pairs | {"a": 1} |
| Array | [] with values | [1, 2, 3] |
Not valid in JSON: undefined, NaN, Infinity, -Infinity, functions, symbols, Date objects, Map, Set, RegExp, or any other JavaScript-specific type.
JSON as a Standalone Value
A valid JSON document does not have to be an object. It can be any JSON value:
// All of these are valid JSON documents:
console.log(JSON.parse('"hello"')); // "hello" (string)
console.log(JSON.parse('42')); // 42 (number)
console.log(JSON.parse('true')); // true (boolean)
console.log(JSON.parse('null')); // null
console.log(JSON.parse('[1,2,3]')); // [1, 2, 3] (array)
console.log(JSON.parse('{"a":1}')); // { a: 1 } (object)
JSON.stringify(): Object to JSON String
JSON.stringify() converts a JavaScript value into a JSON-formatted string.
Basic Usage
let user = {
name: "Alice",
age: 30,
isActive: true,
hobbies: ["reading", "coding"]
};
let json = JSON.stringify(user);
console.log(json);
// '{"name":"Alice","age":30,"isActive":true,"hobbies":["reading","coding"]}'
console.log(typeof json);
// "string"
What Gets Stringified
let data = {
string: "hello",
number: 42,
float: 3.14,
boolean: true,
nullVal: null,
array: [1, 2, 3],
nested: { a: 1, b: 2 }
};
console.log(JSON.stringify(data));
// '{"string":"hello","number":42,"float":3.14,"boolean":true,"nullVal":null,"array":[1,2,3],"nested":{"a":1,"b":2}}'
What Gets Silently Skipped
Properties with certain value types are silently omitted from the output:
let data = {
name: "Alice",
greet: function() { return "hi"; }, // Function (SKIPPED)
id: Symbol("id"), // Symbol (SKIPPED)
nothing: undefined, // undefined (SKIPPED)
age: 30
};
console.log(JSON.stringify(data));
// '{"name":"Alice","age":30}'
// greet, id, and nothing are gone!
Arrays Treat Skipped Values Differently
In arrays, unsupported values are replaced with null instead of being removed (to preserve indices):
let arr = [1, function() {}, undefined, Symbol("x"), "hello"];
console.log(JSON.stringify(arr));
// '[1,null,null,null,"hello"]'
// Functions, undefined, and symbols become null in arrays
Special Number Values
console.log(JSON.stringify(NaN)); // "null"
console.log(JSON.stringify(Infinity)); // "null"
console.log(JSON.stringify(-Infinity)); // "null"
let data = { a: NaN, b: Infinity, c: -Infinity };
console.log(JSON.stringify(data));
// '{"a":null,"b":null,"c":null}'
Stringifying Primitives
JSON.stringify works with standalone values too:
console.log(JSON.stringify("hello")); // '"hello"' (with quotes)
console.log(JSON.stringify(42)); // '42'
console.log(JSON.stringify(true)); // 'true'
console.log(JSON.stringify(null)); // 'null'
console.log(JSON.stringify(undefined)); // undefined (not a string!)
Note that JSON.stringify(undefined) returns the JavaScript value undefined, not the string "undefined". This is a special case.
Date Objects
Dates are automatically converted to their ISO string representation:
let event = {
title: "Meeting",
date: new Date("2024-01-15T14:30:00Z")
};
console.log(JSON.stringify(event));
// '{"title":"Meeting","date":"2024-01-15T14:30:00.000Z"}'
// The date becomes a STRING in JSON, not a Date object
// When parsed back, it will be a string, not a Date!
Replacer Function and Array: Controlling Serialization
JSON.stringify() accepts a second argument called the replacer that controls which properties are included and how values are transformed.
Replacer as an Array
Pass an array of property names to include only those properties:
let user = {
name: "Alice",
age: 30,
password: "secret123",
email: "alice@example.com",
role: "admin"
};
// Only include name and email
let json = JSON.stringify(user, ["name", "email"]);
console.log(json);
// '{"name":"Alice","email":"alice@example.com"}'
This is useful for filtering out sensitive data or selecting specific fields for an API response.
Array Replacer with Nested Objects
The array replacer applies to all levels of nesting. You must include the keys of nested properties too:
let data = {
name: "Alice",
address: {
city: "Rome",
zip: "00100",
country: "Italy"
}
};
// Must include "address" AND the nested keys you want
let json = JSON.stringify(data, ["name", "address", "city"]);
console.log(json);
// '{"name":"Alice","address":{"city":"Rome"}}'
// Without "address" in the array, the entire nested object is excluded
let json2 = JSON.stringify(data, ["name", "city"]);
console.log(json2);
// '{"name":"Alice"}' ()"city" is nested inside "address" which was excluded)
Replacer as a Function
A replacer function receives each key-value pair and can transform or filter values:
let user = {
name: "Alice",
age: 30,
password: "secret123",
loginCount: 42,
lastLogin: new Date("2024-01-15")
};
let json = JSON.stringify(user, (key, value) => {
// key is "" for the root object itself
if (key === "password") return undefined; // Exclude
if (key === "age") return value + " years"; // Transform
return value; // Keep everything else
});
console.log(json);
// '{"name":"Alice","age":"30 years","loginCount":42,"lastLogin":"2024-01-15T00:00:00.000Z"}'
The replacer function is called for every key-value pair, including nested ones. The first call has an empty string "" as the key, representing the root value:
let data = { a: { b: 2 }, c: 3 };
JSON.stringify(data, (key, value) => {
console.log(`Key: "${key}", Value:`, value);
return value;
});
// Key: "", Value: { a: { b: 2 }, c: 3 } ← root object
// Key: "a", Value: { b: 2 }
// Key: "b", Value: 2
// Key: "c", Value: 3
Practical: Censoring Sensitive Fields
function censorSensitive(key, value) {
const sensitiveKeys = ["password", "token", "secret", "creditCard"];
if (sensitiveKeys.includes(key)) {
return "***REDACTED***";
}
return value;
}
let apiResponse = {
user: "Alice",
token: "eyJhbGciOiJIUzI1NiJ9...",
data: {
creditCard: "4111-1111-1111-1111",
balance: 1000
}
};
console.log(JSON.stringify(apiResponse, censorSensitive, 2));
// {
// "user": "Alice",
// "token": "***REDACTED***",
// "data": {
// "creditCard": "***REDACTED***",
// "balance": 1000
// }
// }
The space Parameter: Pretty Printing
The third argument to JSON.stringify() controls indentation for human-readable output.
Numeric Indentation
let user = { name: "Alice", age: 30, hobbies: ["reading", "coding"] };
// Compact (default)
console.log(JSON.stringify(user));
// '{"name":"Alice","age":30,"hobbies":["reading","coding"]}'
// 2-space indentation (most common)
console.log(JSON.stringify(user, null, 2));
// {
// "name": "Alice",
// "age": 30,
// "hobbies": [
// "reading",
// "coding"
// ]
// }
// 4-space indentation
console.log(JSON.stringify(user, null, 4));
// {
// "name": "Alice",
// "age": 30,
// "hobbies": [
// "reading",
// "coding"
// ]
// }
String Indentation
You can use a string (up to 10 characters) instead of a number:
let data = { a: 1, b: 2 };
console.log(JSON.stringify(data, null, "\t"));
// {
// "a": 1,
// "b": 2
// }
console.log(JSON.stringify(data, null, "-->"));
// {
// -->"a": 1,
// -->"b": 2
// }
Combining Replacer and Space
All three arguments work together:
let user = {
name: "Alice",
password: "secret",
settings: { theme: "dark", lang: "en" }
};
let json = JSON.stringify(
user,
(key, value) => key === "password" ? undefined : value,
2
);
console.log(json);
// {
// "name": "Alice",
// "settings": {
// "theme": "dark",
// "lang": "en"
// }
// }
toJSON(): Custom Serialization
If an object has a toJSON() method, JSON.stringify() calls it and uses its return value instead of the object itself.
How toJSON Works
let meeting = {
title: "Sprint Planning",
date: new Date("2024-01-15T14:30:00Z"),
duration: 60
};
// Date already has a built-in toJSON method:
console.log(meeting.date.toJSON());
// "2024-01-15T14:30:00.000Z"
// This is why dates appear as ISO strings in JSON output
console.log(JSON.stringify(meeting));
// '{"title":"Sprint Planning","date":"2024-01-15T14:30:00.000Z","duration":60}'
Custom toJSON
let room = {
number: 101,
capacity: 20,
equipment: ["projector", "whiteboard", "microphone"],
toJSON() {
return {
id: `room-${this.number}`,
seats: this.capacity,
hasProjector: this.equipment.includes("projector")
};
}
};
console.log(JSON.stringify(room, null, 2));
// {
// "id": "room-101",
// "seats": 20,
// "hasProjector": true
// }
The toJSON method completely replaces the default serialization. The return value is what gets stringified.
toJSON in Nested Objects
let schedule = {
event: "Conference",
room: {
name: "Main Hall",
toJSON() {
return this.name; // Serialize as just the name string
}
}
};
console.log(JSON.stringify(schedule));
// '{"event":"Conference","room":"Main Hall"}'
toJSON Can Return Any JSON-Compatible Value
let counter = {
value: 42,
toJSON() {
return this.value; // Returns a number, not an object
}
};
console.log(JSON.stringify(counter));
// '42'
console.log(JSON.stringify({ count: counter }));
// '{"count":42}'
The toJSON Call Order
When JSON.stringify() encounters an object, it follows this order:
- If the object has a
toJSON()method, call it and use the return value - Apply the replacer function (if provided) to the result
- Recursively stringify the result
let obj = {
value: 42,
toJSON() {
return { transformed: this.value * 2 };
}
};
// toJSON runs first, then replacer
let json = JSON.stringify(obj, (key, value) => {
if (key === "transformed") return value + 1;
return value;
});
console.log(json);
// '{"transformed":85}'
// toJSON: { transformed: 84 } → replacer changes 84 to 85
JSON.parse(): JSON String to Object
JSON.parse() converts a JSON string back into a JavaScript value.
Basic Usage
let json = '{"name":"Alice","age":30,"hobbies":["reading","coding"]}';
let user = JSON.parse(json);
console.log(user.name); // "Alice"
console.log(user.age); // 30
console.log(user.hobbies); // ["reading", "coding"]
console.log(typeof user); // "object"
Parsing Different JSON Values
console.log(JSON.parse('"hello"')); // "hello" (string)
console.log(JSON.parse('42')); // 42 (number)
console.log(JSON.parse('true')); // true (boolean)
console.log(JSON.parse('null')); // null
console.log(JSON.parse('[1, 2, 3]')); // [1, 2, 3] (array)
Invalid JSON Throws SyntaxError
// All of these throw SyntaxError:
// JSON.parse("undefined"); // undefined is not valid JSON
// JSON.parse("{'name': 'Alice'}"); // Single quotes not allowed
// JSON.parse("{name: 'Alice'}"); // Unquoted keys not allowed
// JSON.parse('{"a": 1,}'); // Trailing comma
// JSON.parse(""); // Empty string
// JSON.parse("hello"); // Unquoted string
Safe Parsing
Always wrap JSON.parse() in a try-catch when parsing external data:
function safeParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("Invalid JSON:", error.message);
return fallback;
}
}
console.log(safeParse('{"valid": true}')); // { valid: true }
console.log(safeParse('not json')); // null
console.log(safeParse('not json', {})); // {} (custom fallback)
console.log(safeParse('not json', [])); // [] (custom fallback)
The Reviver Function: Custom Deserialization
JSON.parse() accepts a second argument called the reviver, a function that transforms each key-value pair during parsing. This is the inverse of the replacer in JSON.stringify().
Basic Reviver
let json = '{"name":"Alice","age":"30","score":"95.5"}';
// Without reviver: all values are strings as stored
let raw = JSON.parse(json);
console.log(typeof raw.age); // "string"
// With reviver: convert numeric strings to numbers
let parsed = JSON.parse(json, (key, value) => {
if (key === "age" || key === "score") return Number(value);
return value;
});
console.log(typeof parsed.age); // "number"
console.log(typeof parsed.score); // "number"
console.log(parsed.age); // 30
Reviving Dates
The most common use of the reviver is converting date strings back to Date objects:
let json = '{"title":"Meeting","date":"2024-01-15T14:30:00.000Z","created":"2024-01-10T09:00:00.000Z"}';
let event = JSON.parse(json, (key, value) => {
// Check if the value looks like an ISO date string
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
let date = new Date(value);
if (!isNaN(date)) return date;
}
return value;
});
console.log(event.date instanceof Date); // true
console.log(event.date.getFullYear()); // 2024
console.log(event.created instanceof Date); // true
Reviver Processing Order
The reviver processes values bottom-up (leaves first, then parents). The last call has the key "" for the root:
let json = '{"a": {"b": 2}, "c": 3}';
JSON.parse(json, (key, value) => {
console.log(`Key: "${key}", Value:`, value);
return value;
});
// Key: "b", Value: 2 ← deepest first
// Key: "a", Value: { b: 2 } ← then parent
// Key: "c", Value: 3
// Key: "", Value: { a: { b: 2 }, c: 3 } ← root last
Excluding Properties with Reviver
Returning undefined from a reviver removes the property:
let json = '{"name":"Alice","password":"secret","age":30,"token":"abc123"}';
let safe = JSON.parse(json, (key, value) => {
if (key === "password" || key === "token") return undefined;
return value;
});
console.log(safe); // { name: "Alice", age: 30 }
Limitations: Functions, undefined, Symbols, Circular References
What JSON Cannot Represent
let complex = {
func: function() { return 42; },
arrow: () => "hello",
undef: undefined,
sym: Symbol("id"),
regex: /pattern/gi,
date: new Date("2024-01-15"),
map: new Map([["a", 1]]),
set: new Set([1, 2, 3]),
nan: NaN,
inf: Infinity,
negInf: -Infinity,
bigint: 42n
};
let json = JSON.stringify(complex);
let parsed = JSON.parse(json);
console.log(parsed);
// {
// regex: {}, ← RegExp becomes empty object
// date: "2024-01-15...", ← Date becomes string
// map: {}, ← Map becomes empty object
// set: {}, ← Set becomes empty object
// nan: null, ← NaN becomes null
// inf: null, ← Infinity becomes null
// negInf: null ← -Infinity becomes null
// }
// func, arrow, undef, sym are completely gone (skipped)
// bigint throws TypeError: Do not know how to serialize a BigInt
Summary of Limitations
| Type | stringify Behavior | parse Result |
|---|---|---|
| Functions | Skipped | Missing |
undefined | Skipped (in objects), null (in arrays) | Missing / null |
| Symbols | Skipped | Missing |
NaN | null | null |
Infinity / -Infinity | null | null |
Date | ISO string | String (not Date!) |
RegExp | {} | Empty object |
Map / Set | {} | Empty object |
BigInt | TypeError | N/A |
| Circular references | TypeError | N/A |
Circular References
Objects that reference themselves (directly or indirectly) cause JSON.stringify to throw:
let obj = { name: "self" };
obj.self = obj; // Circular reference
// TypeError: Converting circular structure to JSON
// JSON.stringify(obj);
let a = {};
let b = { ref: a };
a.ref = b; // Indirect circular reference
// TypeError: Converting circular structure to JSON
// JSON.stringify(a);
Unlike other unsupported types that are silently skipped or converted to null, BigInt throws a TypeError when you try to stringify it:
// TypeError: Do not know how to serialize a BigInt
// JSON.stringify({ value: 42n });
// Workaround: convert to string first
let data = { value: 42n };
let json = JSON.stringify(data, (key, value) =>
typeof value === "bigint" ? value.toString() : value
);
console.log(json); // '{"value":"42"}'
Deep Cloning with JSON.parse(JSON.stringify()): Pros and Cons
Before structuredClone() existed, the JSON round-trip was the most common way to create deep clones of objects.
How It Works
let original = {
name: "Alice",
address: {
city: "Rome",
coordinates: { lat: 41.9, lng: 12.5 }
},
hobbies: ["reading", "coding"]
};
let clone = JSON.parse(JSON.stringify(original));
// Deep clone: nested objects are independent
clone.address.city = "Milan";
clone.hobbies.push("hiking");
console.log(original.address.city); // "Rome" (unchanged)
console.log(original.hobbies); // ["reading", "coding"] (unchanged)
Pros
- Works in every JavaScript environment (no polyfills needed)
- Simple one-line syntax
- Handles deeply nested structures
- Well understood and widely used
Cons: Data Loss
The JSON round-trip silently transforms or drops data:
let original = {
name: "Alice",
createdAt: new Date("2024-01-15"),
pattern: /test/gi,
callback: () => console.log("hi"),
nothing: undefined,
count: NaN,
tags: new Set(["a", "b"]),
metadata: new Map([["key", "value"]])
};
let clone = JSON.parse(JSON.stringify(original));
console.log(clone.createdAt); // "2024-01-15T00:00:00.000Z" (STRING, not Date!)
console.log(clone.createdAt instanceof Date); // false
console.log(clone.pattern); // {} (empty object, not RegExp!)
console.log(clone.callback); // undefined (function is gone!)
console.log("nothing" in clone); // false (undefined property removed!)
console.log(clone.count); // null (NaN became null!)
console.log(clone.tags); // {} (Set became empty object!)
console.log(clone.metadata); // {} (Map became empty object!)
Cons: Fails on Circular References
let obj = { name: "circular" };
obj.self = obj;
// TypeError: Converting circular structure to JSON
// JSON.parse(JSON.stringify(obj));
structuredClone() vs. JSON for Deep Cloning
structuredClone() (available in modern browsers and Node.js 17+) is the modern alternative for deep cloning.
Comparison
let original = {
name: "Alice",
date: new Date("2024-01-15"),
data: new Map([["key", "value"]]),
tags: new Set([1, 2, 3]),
buffer: new ArrayBuffer(8),
pattern: /test/gi
};
// JSON method: loses type information
let jsonClone = JSON.parse(JSON.stringify(original));
console.log(jsonClone.date instanceof Date); // false (it's a string)
console.log(jsonClone.data instanceof Map); // false (it's {})
console.log(jsonClone.tags instanceof Set); // false (it's {})
// structuredClone: preserves types
let structClone = structuredClone(original);
console.log(structClone.date instanceof Date); // true ✅
console.log(structClone.data instanceof Map); // true ✅
console.log(structClone.tags instanceof Set); // true ✅
console.log(structClone.pattern instanceof RegExp); // true ✅
Circular References
let obj = { name: "circular" };
obj.self = obj;
// JSON: throws TypeError
// JSON.parse(JSON.stringify(obj));
// structuredClone: handles it correctly
let clone = structuredClone(obj);
console.log(clone.self === clone); // true (circular structure preserved)
console.log(clone.self === obj); // false (different object)
Complete Comparison Table
| Feature | JSON.parse(JSON.stringify()) | structuredClone() |
|---|---|---|
| Nested objects/arrays | Yes | Yes |
Date | Becomes string | Preserved |
Map / Set | Becomes {} | Preserved |
RegExp | Becomes {} | Preserved |
ArrayBuffer / TypedArray | Loses data | Preserved |
| Circular references | Throws error | Handled |
| Functions | Silently dropped | Throws error |
| Symbols | Silently dropped | Throws error |
undefined values | Dropped/null | Preserved |
NaN / Infinity | Becomes null | Preserved |
| Prototype chain | Lost | Lost |
| DOM nodes | N/A | Cloneable |
| Performance | Moderate | Generally faster |
| Browser support | Universal | Modern browsers, Node 17+ |
When to Use Which
// ✅ Use structuredClone() for deep cloning
let clone1 = structuredClone(complexObject);
// ✅ Use JSON when you specifically need a JSON string
let jsonString = JSON.stringify(data);
// ... send over network, save to file, etc.
let parsed = JSON.parse(jsonString);
// ✅ Use JSON round-trip only for simple, JSON-safe data in legacy environments
let simpleClone = JSON.parse(JSON.stringify({ a: 1, b: "hello", c: [1, 2] }));
For deep cloning, always prefer structuredClone() in modern environments. It handles more types correctly, supports circular references, and is generally more performant. Use the JSON method only when you need to support very old environments or when you specifically need JSON serialization.
Practical Patterns
Storing and Retrieving from localStorage
// Save to localStorage
let settings = { theme: "dark", fontSize: 16, language: "en" };
localStorage.setItem("settings", JSON.stringify(settings));
// Retrieve from localStorage
let stored = localStorage.getItem("settings");
let parsed = stored ? JSON.parse(stored) : { theme: "light", fontSize: 14, language: "en" };
console.log(parsed.theme); // "dark"
API Communication
// Sending JSON to an API
async function createUser(userData) {
let response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData) // Object → JSON string
});
let result = await response.json(); // JSON string → Object (built-in)
return result;
}
Configuration Files
// Reading a JSON config file (Node.js)
// import config from "./config.json" assert { type: "json" };
// Or manually:
// let configText = fs.readFileSync("./config.json", "utf-8");
// let config = JSON.parse(configText);
Pretty Printing for Debugging
let complexData = {
users: [
{ name: "Alice", roles: ["admin", "user"] },
{ name: "Bob", roles: ["user"] }
],
settings: { debug: true, version: "2.0" }
};
// Quick way to inspect complex objects
console.log(JSON.stringify(complexData, null, 2));
Summary
- JSON is a text-based data exchange format that supports strings, numbers, booleans, null, objects, and arrays. Property names must be double-quoted strings. No trailing commas, comments, or JavaScript-specific types.
JSON.stringify(value, replacer, space)converts JavaScript values to JSON strings. Functions,undefined, and symbols are silently skipped. Dates become ISO strings.NaNandInfinitybecomenull.- The replacer (second argument) controls serialization: pass an array of property names to include, or a function to transform/filter values.
- The space (third argument) controls indentation. Use
2for readable output,nullor omit for compact output. toJSON()on an object lets you customize what gets serialized. The return value replaces the entire object in the output.JSON.parse(string, reviver)converts JSON strings back to JavaScript values. Always wrap in try-catch when parsing external data.- The reviver (second argument) transforms values during parsing. Essential for converting date strings back to
Dateobjects. - JSON cannot represent functions,
undefined, symbols,RegExp,Map,Set,BigInt, or circular references.BigIntand circular references throw errors; others are silently lost or transformed. JSON.parse(JSON.stringify())for deep cloning works for simple, JSON-safe data but loses type information for Dates, Maps, Sets, and other non-JSON types.structuredClone()is the modern alternative for deep cloning. It preserves Dates, Maps, Sets, RegExp, handles circular references, and is generally preferred over the JSON method.