What Is JSON.stringify?
JSON.stringify converts a JavaScript value into a JSON string. It's
the serialization side of the JSON lifecycle — data going into storage, over a network, or into
a log.
The syntax:
JSON.stringify(value, replacer, space)
The value argument is required — any JavaScript value (object, array, string, number,
boolean, null). The replacer and space arguments are optional.
What gets converted and what gets dropped:
- Objects — serialized as JSON objects
- Arrays — serialized as JSON arrays
- Strings, numbers, booleans, null — serialized as-is
- undefined — dropped from objects (property removed); converted to
nullin arrays - Functions — silently dropped from both objects and arrays
- Symbols — silently dropped from both objects and arrays (even Symbol keys are ignored)
- BigInt — throws
TypeError: Do not know how to serialize a BigInt - Date objects — serialized as an ISO string (e.g.,
"2026-06-11T00:00:00.000Z"), not a Date instance - Circular references — throws
TypeError: Converting circular structure to JSON
Pretty-Printing with the space Parameter
The third argument controls indentation:
const obj = { name: 'Alice', age: 30 };
// Compact single-line output
JSON.stringify(obj);
// '{"name":"Alice","age":30}'
// Pretty-printed with 2-space indent
JSON.stringify(obj, null, 2);
// {
// "name": "Alice",
// "age": 30
// }
// Custom indent string (tab character)
JSON.stringify(obj, null, '\t')
// {
// "name": "Alice",
// "age": 30
// }
For visual formatting without writing code, use the free JSON Formatter — paste your JSON and get colorized, indented output instantly.
What Is JSON.parse?
JSON.parse converts a JSON string back into a JavaScript value. It's
the deserialization side — data coming from storage, an API response, or localStorage.
The syntax:
JSON.parse(text, reviver)
The text argument is the JSON string to parse. The reviver is an
optional function for transforming values during parsing (covered below).
JSON.parse throws a SyntaxError if the input is not valid JSON.
This is why you should always wrap it in a try/catch:
try {
const data = JSON.parse(userInput);
console.log(data.name);
} catch (err) {
console.error('Invalid JSON:', err.message);
// Show user-friendly error, don't silently fail
}
JSON.stringify vs JSON.parse — Side-by-Side
JSON.stringify |
JSON.parse |
|
|---|---|---|
| Input | JavaScript value (object, array, primitive) | JSON string (text) |
| Output | JSON string | JavaScript value |
| Direction | Serialization | Deserialization |
| Common use | Storing data, logging, sending over network | Reading API responses, localStorage, config files |
| Throws on | Circular reference, BigInt, function (if in a context that errors) | Malformed JSON, invalid syntax |
| Second param | replacer (function or array) |
reviver (function) |
| Third param | space (indent) |
None |
Edge Cases That Break JSON.stringify
1. BigInt — TypeError
BigInt is not part of the JSON specification. JSON.stringify throws:
const big = BigInt(9007199254740993);
// TypeError: Do not know how to serialize a BigInt
Fix: Convert BigInts to strings before stringifying, or use structuredClone()
which handles BigInt natively.
2. Circular References — TypeError
If an object references itself (directly or through a chain), JSON.stringify throws:
const obj = { name: 'Alice' };
obj.self = obj; // circular!
// TypeError: Converting circular structure to JSON
JSON.stringify(obj);
Fix options:
-
Replacer array — restrict which properties are serialized:
JSON.stringify(obj, ['name', 'age']); // only serializes name and age -
Replacer function with WeakMap — replace circular values:
function safeStringify(obj) { const seen = new WeakMap(); return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) return '[Circular]'; seen.set(value, true); } return value; }); } -
flatted library — handles circular references natively:
import { stringify } from 'flatted'; const json = stringify(obj); // works, no error
3. undefined — Silently Dropped (Not Always)
This one catches people. undefined behaves differently in objects vs. arrays:
// In objects: property is removed entirely
const obj = { a: 1, b: undefined, c: 3 };
JSON.stringify(obj); // '{"a":1,"c":3}' ← b is gone
// In arrays: undefined becomes null
const arr = [1, undefined, 3];
JSON.stringify(arr); // '[1,null,3]' ← replaced with null
This matters when you're round-tripping data — properties that were undefined
become missing (not undefined), and array positions that were undefined become
null.
4. Symbol Keys — Ignored
Symbol-keyed properties are always skipped in serialization, even when using a replacer:
const key = Symbol('id');
const obj = { [key]: 123, name: 'Alice' };
JSON.stringify(obj); // '{"name":"Alice"}' ← Symbol key dropped
5. Date Objects — Becomes a String
Dates become ISO strings. They do not survive JSON.parse as Date instances:
const event = { name: 'Launch', date: new Date('2026-06-11T00:00:00Z') };
const json = JSON.stringify(event);
// '{"name":"Launch","date":"2026-06-11T00:00:00.000Z"}'
const parsed = JSON.parse(json);
parsed.date instanceof Date // false — it's a string!
parsed.date // "2026-06-11T00:00:00.000Z"
Use the reviver function to restore Date objects after parsing (shown below).
Edge Cases That Break JSON.parse
1. Trailing Commas
JSON does not allow trailing commas. This is the most common JSON parse error in practice:
// This is valid JavaScript — but not valid JSON
const jsObj = { name: 'Alice', age: 30, };
JSON.parse('{ "name": "Alice", "age": 30, }');
// SyntaxError: Unexpected token } in JSON at position 34
// Correct:
JSON.parse('{ "name": "Alice", "age": 30 }');
The JSON Validator catches trailing commas and other syntax errors before they hit your code.
2. Single Quotes
JSON requires double quotes for all strings. Single quotes are invalid:
// SyntaxError: Unexpected token ' in JSON at position 1
JSON.parse("{ 'name': 'Alice' }");
// Correct:
JSON.parse('{ "name": "Alice" }');
3. JavaScript Comments
Comments are valid in JavaScript objects but not in JSON:
// SyntaxError: Unexpected token / in JSON at position 12
JSON.parse('{ "name": "Alice" // this is a comment }');
// Also invalid:
JSON.parse('{ "name": "Alice" /* block comment */ }');
This is why you can't just copy a JavaScript object literal as JSON — you need to remove comments and double-quote all keys and string values.
4. Unquoted Keys
JavaScript object literals allow bare keys; JSON requires all keys to be double-quoted:
// SyntaxError: Unexpected token n in JSON at position 2
JSON.parse('{ name: "Alice" }');
// Correct:
JSON.parse('{ "name": "Alice" }');
JSON.parse in a try/catch. A
malformed JSON string from an API response or user input will crash your code without one.
And validate before parsing — use the JSON Validator to catch
errors before they reach JSON.parse.
The Replacer Function
The second argument to JSON.stringify can be a function or an array. It gives you
fine-grained control over what gets serialized and how.
Replacer as an Array — Whitelist Properties
const user = {
id: 42,
name: 'Alice',
email: 'alice@example.com',
passwordHash: '3f1a9...',
createdAt: '2026-06-11'
};
// Only include name and id — drop email, password, etc.
const safe = JSON.stringify(user, ['id', 'name']);
// '{"id":42,"name":"Alice"}'
This is the simplest way to exclude sensitive fields from a serialized payload.
Replacer as a Function — Transform or Skip
const user = {
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date('2026-06-11'),
role: 'admin'
};
// Transform role values (security masking)
const safe = JSON.stringify(user, (key, value) => {
if (key === 'email') return '[REDACTED]';
if (key === 'role') return 'user'; // downgrade role for logging
return value;
});
// '{"name":"Alice","email":"[REDACTED]","createdAt":"2026-06-11T00:00:00.000Z","role":"user"}'
The replacer is called for every key-value pair. A few gotchas:
-
The root call — when replacer is called for the root object,
keyis an empty string''. Checkif (key === '')to handle the root separately. -
Return
undefined— skips that property entirely (not just set to undefined, it is removed from output). - Circular references in replacer — still your responsibility to handle. The replacer does not prevent the circular error; it just lets you intercept values before they fail.
The Reviver Function
The second argument to JSON.parse transforms values during parsing. It's called
for every key-value pair with the parsed JSON, allowing you to restore types that JSON
doesn't natively support.
Restoring Date Objects
const json = '{"name":"Launch","date":"2026-06-11T00:00:00.000Z"}';
const parsed = JSON.parse(json, (key, value) => {
// Detect ISO 8601 date strings and convert to Date
if (
typeof value === 'string' &&
/^\\d{4}-\\d{2}-\\d{2}T/.test(value)
) {
return new Date(value);
}
return value;
});
parsed.date instanceof Date // true!
Sanitization with Reviver
// Strip HTML tags from string values
const dirty = '{"bio":"Hello"}';
const clean = JSON.parse(dirty, (key, value) => {
if (typeof value === 'string') {
return value.replace(/<[^>]*>/g, '');
}
return value;
});
// { bio: 'Hello' }
Providing Default Values
const partial = '{"name":"Alice"}'; // missing age and role
const withDefaults = JSON.parse(partial, (key, value) => {
if (key !== '' && value === undefined) return null; // missing fields → null
return value;
});
// { name: 'Alice', age: null, role: null }
Common Real-World Patterns
Deep Cloning (and Its Limits)
JSON.parse(JSON.stringify(obj)) is a classic deep clone pattern:
const original = { name: 'Alice', scores: [95, 87, 91] };
const clone = JSON.parse(JSON.stringify(original));
clone.scores.push(99);
original.scores // [95, 87, 91] — unchanged (deep clone worked)
clone.name // 'Alice'
What gets lost in this clone:
undefinedproperties are dropped- Functions are dropped
- Symbol-keyed properties are dropped
- BigInt throws a TypeError
- Circular references throw a TypeError
- Date objects become strings
For most modern use cases, use structuredClone(obj) instead:
// built-in since 2021, handles BigInt, Dates, Maps, Sets, circular refs
const clone = structuredClone(original);
localStorage
localStorage only stores strings — JSON.stringify/parse is the standard pattern:
// Save
const userPrefs = { theme: 'dark', language: 'en', notifications: true };
localStorage.setItem('prefs', JSON.stringify(userPrefs));
// Load
const stored = localStorage.getItem('prefs');
if (stored) {
const prefs = JSON.parse(stored); // always try/catch in production
}
API Response Handling
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
// Always parse JSON inside try/catch — network responses are untrusted
let data;
try {
data = await res.json(); // .json() is just JSON.parse under the hood
} catch (err) {
throw new Error('Invalid JSON response from API');
}
// Validate structure before using
if (!data || typeof data !== 'object') {
throw new Error('Unexpected response format');
}
return data;
}
Performance Considerations
| Concern | Details |
|---|---|
| Large payloads (>1MB) | JSON.parse blocks the main thread. For large payloads in a browser, consider
Web Workers to parse off the main thread without freezing the UI. |
| Hot paths / tight loops | Profile first. For 100K+ serialize/parse cycles, JSON is rarely the bottleneck —
but microbenchmarks reveal the truth. JSON.parse is faster than
eval() or custom parsers for most inputs. |
| Streaming | Both methods are synchronous and work on the full input at once. For streaming
JSON, use ReadableStream + JSON.parse incrementally, or
libraries like oboe.js that support progressive parsing. |
| Memory | For deep objects, JSON creates intermediate strings before the GC cleans them up. StructuredClone avoids this intermediate string and can be more memory-efficient for very large objects. |
JSON.parse(JSON.stringify(obj))
purely for deep cloning, replace it with structuredClone(obj). It's built into
all modern browsers and Node.js 17+, handles BigInt and circular references correctly,
preserves Date type (unlike JSON), and is faster for large objects.
Need to format, validate, or compare JSON without writing code? Our free tools do it all in-browser — no signup, no data sent to any server.
Open JSON FormatterFrequently Asked Questions
'[Circular]', or use the flatted library which handles circular references natively.const copy = JSON.parse(JSON.stringify(obj)) is a common deep clone pattern. However, it loses undefined, functions, Symbols, and BigInt values, and fails on circular references. For most use cases, structuredClone(obj) is a better built-in alternative that handles BigInt and circular references correctly, and preserves Date types.(key, value) => newValue or an array of allowed property names. As a function, it is called for every key-value pair including the root (where key is an empty string). Return undefined to skip a property, or return a new value to transform it. As an array, only the listed property names are included.'\t' for a tab). Example: JSON.stringify(obj, null, 2). For visual formatting without writing code, use the free JSON Formatter.