Every JavaScript developer uses JSON.parse() and JSON.stringify() regularly — they are the two most fundamental methods for working with JSON data. Yet many developers use them without fully understanding their nuances, optional parameters, edge cases, and performance implications. This guide covers everything you need to know about both methods, from basic usage to advanced techniques that senior developers rely on daily.
Quick Comparison
| Feature | JSON.parse() | JSON.stringify() |
|---|---|---|
| Direction | String → Object | Object → String |
| Input | Valid JSON string | JavaScript value |
| Output | JavaScript object/array/value | JSON string |
| Error on invalid input | Throws SyntaxError | Throws TypeError (circular ref) |
| Optional 2nd param | Reviver function | Replacer function or array |
| Optional 3rd param | — | Space (indentation) |
| Common use | Receiving API data | Sending API data |
JSON.parse() — String to Object
JSON.parse() takes a valid JSON string and converts it into a JavaScript value — an object, array, string, number, boolean, or null. This is how you read data from APIs, localStorage, configuration files, and any external source that provides JSON as text.
Basic Syntax
const jsonString = '{"name": "Alice", "age": 30, "isAdmin": true}'
const user = JSON.parse(jsonString)
console.log(user.name) // → "Alice"
console.log(user.age) // → 30
console.log(user.isAdmin) // → true
console.log(typeof user) // → "object"
// Parsing arrays
const arr = JSON.parse('[1, 2, 3, "hello"]')
console.log(arr[0]) // → 1
console.log(arr[3]) // → "hello"
// Parsing primitives
JSON.parse('"hello"') // → "hello" (string)
JSON.parse('42') // → 42 (number)
JSON.parse('true') // → true (boolean)
JSON.parse('null') // → nullThe Reviver Function (2nd Parameter)
The reviver is a function called for each key-value pair during parsing. It lets you transform values as they are parsed — the most common use case is converting date strings back into Date objects:
// Problem: JSON has no Date type — dates become strings
const json = '{"name": "Alice", "createdAt": "2024-05-01T12:00:00.000Z"}'
const parsed = JSON.parse(json)
console.log(typeof parsed.createdAt) // → "string" (not a Date!)
// Solution: Use a reviver to convert date strings
const withDates = JSON.parse(json, (key, value) => {
// Check if value looks like an ISO date string
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value)
}
return value
})
console.log(withDates.createdAt instanceof Date) // → true
console.log(withDates.createdAt.getFullYear()) // → 2024
// Reviver for filtering/transforming values
const filtered = JSON.parse('{"a": 1, "b": 2, "c": 3}', (key, value) => {
if (key === "b") return undefined // Remove this property
if (typeof value === "number") return value * 10 // Multiply numbers
return value
})
// → { a: 10, c: 30 } — "b" was removed, numbers were multipliedError Handling
JSON.parse() throws a SyntaxError when the input is not valid JSON. Always wrap it in try/catch when parsing external data:
// ❌ Dangerous — crashes if input is invalid
const data = JSON.parse(userInput)
// ✅ Safe — handles errors gracefully
function safeParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString)
} catch (error) {
console.error("Invalid JSON:", error.message)
return fallback
}
}
const data = safeParse(userInput, {})
// Returns {} instead of crashing if input is invalid
// Common causes of 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 not allowedJSON.stringify() — Object to String
JSON.stringify() converts a JavaScript value into a JSON string. This is how you send data to APIs, save to localStorage, write to files, or serialize objects for transmission.
Basic Syntax
const user = { name: "Alice", age: 30, isAdmin: true }
const jsonString = JSON.stringify(user)
console.log(jsonString)
// → '{"name":"Alice","age":30,"isAdmin":true}'
console.log(typeof jsonString) // → "string"
// Stringify arrays
JSON.stringify([1, 2, 3]) // → '[1,2,3]'
// Stringify primitives
JSON.stringify("hello") // → '"hello"' (note: wrapped in quotes)
JSON.stringify(42) // → '42'
JSON.stringify(true) // → 'true'
JSON.stringify(null) // → 'null'Pretty Printing (3rd Parameter)
The third parameter controls indentation — pass a number for spaces or a string for custom indent characters:
const user = { name: "Alice", roles: ["admin", "editor"], settings: { theme: "dark" } }
// Compact (default) — for network transmission
JSON.stringify(user)
// → '{"name":"Alice","roles":["admin","editor"],"settings":{"theme":"dark"}}'
// Pretty-printed with 2 spaces — for readability
JSON.stringify(user, null, 2)
// → {
// "name": "Alice",
// "roles": [
// "admin",
// "editor"
// ],
// "settings": {
// "theme": "dark"
// }
// }
// Tab indentation
JSON.stringify(user, null, "\t")The Replacer Function (2nd Parameter)
The replacer controls which properties appear in the output. It can be a function or an array:
const user = { name: "Alice", password: "secret123", email: "alice@example.com", age: 30 }
// Replacer as array — only include specified keys
JSON.stringify(user, ["name", "email"])
// → '{"name":"Alice","email":"alice@example.com"}'
// Replacer as function — transform or filter values
JSON.stringify(user, (key, value) => {
if (key === "password") return undefined // Remove sensitive fields
if (typeof value === "string") return value.toUpperCase()
return value
})
// → '{"name":"ALICE","email":"ALICE@EXAMPLE.COM","age":30}'
// Real-world: Remove null/undefined values
const clean = JSON.stringify(data, (key, value) => {
if (value === null || value === undefined) return undefined
return value
})What JSON.stringify() Omits
Not everything can be serialized to JSON. These values are silently dropped or converted:
const obj = {
name: "Alice",
age: undefined, // ❌ OMITTED — undefined disappears
greet: function() {}, // ❌ OMITTED — functions disappear
id: Symbol("id"), // ❌ OMITTED — Symbols disappear
score: NaN, // ⚠️ Becomes null
value: Infinity, // ⚠️ Becomes null
date: new Date(), // ⚠️ Becomes ISO string (not reversible)
regex: /test/gi, // ⚠️ Becomes {} (empty object)
map: new Map(), // ⚠️ Becomes {} (loses data)
set: new Set([1,2,3]), // ⚠️ Becomes {} (loses data)
}
JSON.stringify(obj)
// → '{"name":"Alice","score":null,"value":null,"date":"2024-05-01T...","regex":{},"map":{},"set":{}}'
// Note: age, greet, and id are completely gone!
// In arrays, omitted values become null:
JSON.stringify([1, undefined, function(){}, 4])
// → '[1,null,null,4]'Real-World Use Cases
API Communication (fetch)
// Sending data to an API (stringify)
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
})
// Receiving data from an API (parse)
const data = await response.json() // Internally calls JSON.parse()
console.log(data.id) // → 123localStorage / sessionStorage
// Storage only accepts strings — must stringify objects
const settings = { theme: "dark", fontSize: 14, language: "en" }
localStorage.setItem("settings", JSON.stringify(settings))
// Reading back — must parse the string
const stored = localStorage.getItem("settings")
const parsed = JSON.parse(stored)
console.log(parsed.theme) // → "dark"
// Safe pattern with fallback
function getStoredSettings() {
try {
return JSON.parse(localStorage.getItem("settings")) || {}
} catch {
return {}
}
}Deep Cloning Objects
// Quick deep clone using parse + stringify
const original = { user: { name: "Alice", scores: [10, 20, 30] } }
const clone = JSON.parse(JSON.stringify(original))
clone.user.name = "Bob"
clone.user.scores.push(40)
console.log(original.user.name) // → "Alice" (unchanged)
console.log(original.user.scores) // → [10, 20, 30] (unchanged)
// ⚠️ Limitations of this approach:
// - Loses undefined, functions, Symbols
// - Converts Dates to strings
// - Fails on circular references
// - Slower than structuredClone()
// ✅ Modern alternative (2022+):
const clone2 = structuredClone(original)
// Handles Dates, Maps, Sets, circular refs — but not functionsComparing Objects
// Objects can't be compared with ===
{ a: 1 } === { a: 1 } // → false (different references)
// Quick equality check using stringify
function shallowEqual(a, b) {
return JSON.stringify(a) === JSON.stringify(b)
}
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }) // → true
shallowEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // → false ⚠️ (key order matters!)
// For order-independent comparison, sort keys first:
function deepEqual(a, b) {
return JSON.stringify(sortKeys(a)) === JSON.stringify(sortKeys(b))
}Common Mistakes Developers Make
⚠️ Double-stringifying data
Calling JSON.stringify() on a value that is already a string produces a double-quoted string. Check if data is already a string before stringifying.
JSON.stringify("hello") // → '"hello"' (wrapped in extra quotes!)⚠️ Double-parsing data
Calling JSON.parse() on a value that is already an object throws an error or produces unexpected results.
JSON.parse({name: 'Alice'}) // ❌ TypeError — input must be a string⚠️ Not handling parse errors
JSON.parse() throws on invalid input. Without try/catch, your entire application can crash from malformed API responses or corrupted localStorage.
// Always: try { JSON.parse(data) } catch { /* handle */ }⚠️ Assuming key order is preserved
JSON spec does not guarantee object key order. While modern engines preserve insertion order, comparing stringified objects for equality is fragile.
JSON.stringify({b:2, a:1}) !== JSON.stringify({a:1, b:2})⚠️ Losing Date objects
Dates become ISO strings after stringify. When you parse them back, they remain strings unless you use a reviver.
JSON.parse(JSON.stringify({d: new Date()})).d // → string, not Date⚠️ Ignoring circular references
Objects that reference themselves (e.g., DOM nodes, parent-child relationships) cause JSON.stringify() to throw a TypeError.
const a = {}; a.self = a; JSON.stringify(a) // ❌ TypeErrorBest Practices
Never assume external JSON is valid. Network errors, API changes, and corrupted data are common.
Before logging or transmitting objects, strip passwords, tokens, and PII with a replacer function.
The parse/stringify trick works but has limitations. structuredClone() is faster and handles more types.
After parsing, validate the structure matches what you expect using Zod, Yup, or JSON Schema.
Always implement a date reviver when working with APIs that return ISO date strings.
JSON.stringify() on very large objects (10MB+) can block the main thread. Consider Web Workers for heavy serialization.
Custom Serialization with toJSON()
If an object has a toJSON() method, JSON.stringify() will call it and use the return value instead of the object itself. This gives you complete control over serialization:
class User {
constructor(name, email, password) {
this.name = name
this.email = email
this.password = password
this.createdAt = new Date()
}
// Custom serialization — exclude password, format date
toJSON() {
return {
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
// password is intentionally excluded
}
}
}
const user = new User("Alice", "alice@example.com", "secret123")
JSON.stringify(user, null, 2)
// → { "name": "Alice", "email": "alice@example.com", "createdAt": "2024-05-01T..." }
// Password is never serialized!Interview Questions
These JSON-related questions commonly appear in JavaScript interviews:
Q: What is the output of JSON.stringify(undefined)?
A: undefined (not the string 'undefined' — the function returns undefined, not a string, because undefined is not a valid JSON value).
Q: How do you deep clone an object without structuredClone?
A: JSON.parse(JSON.stringify(obj)) — but note it loses functions, undefined, Symbols, Dates (become strings), and fails on circular references.
Q: What does JSON.parse(JSON.stringify(new Date())) return?
A: A string (ISO format), not a Date object. JSON has no Date type — dates are serialized as strings and must be manually reconstructed.
Q: How do you handle circular references in JSON.stringify()?
A: Use a replacer function with a WeakSet to track seen objects, or use libraries like flatted or circular-json.
Q: What is the difference between JSON.stringify({a: undefined}) and JSON.stringify({a: null})?
A: The first produces '{}' (undefined properties are omitted). The second produces '{"a":null}' (null is a valid JSON value).
Performance Considerations
JSON.parse() and JSON.stringify() are synchronous and run on the main thread. For most applications, they are fast enough. But for very large payloads, they can cause jank:
- Under 1MB: No concerns — both methods are instant.
- 1-10MB: May cause a brief pause (10-50ms). Usually acceptable.
- 10MB+: Can block the main thread for 100ms+. Consider using a Web Worker.
- For streaming large JSON: Use libraries like
stream-jsonin Node.js or theResponse.json()method in browsers (which is optimized internally).
Pro tip: response.json() in the Fetch API is generally faster than manually calling JSON.parse(await response.text())because browsers can optimize the internal parsing pipeline.
Handling Types That JSON Does Not Support
JSON only supports six data types: string, number, boolean, null, object, and array. JavaScript has many more types that need special handling when serializing and deserializing. Understanding how each type behaves prevents data loss in your applications.
Dates
Dates are the most common problem. JSON.stringify() converts Date objects to ISO 8601 strings automatically (because Date has a built-in toJSON method). But JSON.parse() does not convert them back — you get plain strings. This means a round-trip through JSON silently changes your Date objects into strings, which can cause subtle bugs when you later try to call Date methods on them.
The solution is to always use a reviver function that detects ISO date patterns and reconstructs Date objects during parsing. Alternatively, store dates as Unix timestamps (numbers) which survive the round-trip without any special handling, though you lose human readability.
Maps and Sets
Map and Set are modern JavaScript collection types that JSON.stringify() cannot handle correctly. Both serialize to empty objects because JSON has no concept of Map or Set. If you need to serialize these types, convert them to arrays first: use Array.from(map) for Maps (produces an array of key-value pairs) and Array.from(set) for Sets (produces an array of values). During parsing, reconstruct them with new Map(array) and new Set(array).
BigInt
BigInt values cause JSON.stringify() to throw a TypeError — it does not silently convert or omit them like it does with undefined. This is because there is no safe way to represent arbitrary-precision integers in JSON (regular JSON numbers are IEEE 754 doubles, which lose precision beyond 2^53). The workaround is to convert BigInts to strings before stringifying, and parse them back with a reviver that detects numeric strings that need BigInt conversion.
Circular References
Circular references occur when an object references itself directly or indirectly — for example, a parent object that has a children array where each child points back to the parent. DOM nodes are the most common source of circular references in web development. JSON.stringify() throws a TypeError on circular structures. Solutions include using a replacer function that tracks seen objects with a WeakSet and skips duplicates, using the flatted library which preserves circular references as special tokens, or restructuring your data to avoid cycles.
Functions and Classes
Functions, class instances, and Symbol values are completely invisible to JSON serialization. Properties with function values are silently omitted. Class instances lose their prototype chain — they become plain objects with only own enumerable properties. If you need to serialize class instances, implement a toJSON() method that returns a plain object representation, and create a static fromJSON() method to reconstruct the instance during parsing.
JSON.parse and JSON.stringify in Node.js vs Browser
While JSON.parse() and JSON.stringify() have the same API in both environments, there are practical differences in how and when you use them:
Browser Context
In the browser, you most commonly use these methods with the Fetch API (serializing request bodies, parsing responses), localStorage/sessionStorage (which only stores strings), Web Workers (passing data between threads via structured cloning or JSON), and WebSocket messages (which are transmitted as text). The browser's response.json() method is optimized and should be preferred over manually calling JSON.parse(await response.text()).
Node.js Context
In Node.js, additional use cases include reading and writing JSON configuration files (package.json, tsconfig.json), parsing request bodies in Express/Fastify middleware, streaming JSON from databases and APIs, and inter-process communication. Node.js also provides require() which can directly import JSON files without manual parsing, and the fs module for reading JSON files from disk.
For large JSON files in Node.js, consider stream-based parsing with libraries like JSONStream or stream-json, which process data incrementally without loading the entire file into memory. This is essential when processing multi-gigabyte JSON log files or database exports.
Security Considerations
In older JavaScript environments, developers used eval() to parse JSON strings — this is extremely dangerous because eval executes arbitrary code. JSON.parse() is safe because it only parses data structures and never executes code. However, you should still validate the parsed output before trusting it. An attacker cannot inject code through JSON.parse(), but they can provide unexpected data shapes that break your application logic if not validated. Always validate parsed API responses against expected schemas before using the data.
Frequently Asked Questions
What is the difference between JSON.parse() and JSON.stringify()?
What happens if JSON.parse() receives invalid JSON?
Can JSON.stringify() handle circular references?
Does JSON.stringify() preserve undefined values?
How do I pretty-print JSON in JavaScript?
Is JSON.parse() safe to use with user input?
What is the reviver function in JSON.parse()?
What is the replacer function in JSON.stringify()?
Related JSON Tools
Summary
JSON.parse() and JSON.stringify() are inverse operations — one converts strings to objects, the other converts objects to strings. Master their optional parameters (reviver, replacer, space) to handle dates, filter sensitive data, and produce readable output. Always handle parse errors with try/catch, be aware of what stringify omits (undefined, functions, Symbols), and consider performance for large payloads. These two methods are the foundation of all JSON data exchange in JavaScript applications.
