Json.parse and json.stringify in JavaScript
JSON.stringify() converts a JavaScript value to a JSON string. JSON.parse() converts a JSON string back to a JavaScript value.
Theory
TL;DR
stringifypacks an object into a portable text string;parseunpacks it back into a working object.stringifydrops functions,undefined, and Symbols silently; Dates become ISO strings;BigIntthrows aTypeError.parsevalidates strictly and throwsSyntaxErroron any malformed input.- Use both for: API request bodies, localStorage, config files on disk.
- Skip both for: Dates you need as Date objects, functions, or circular references.
Quick example
const user = { name: 'Alice', age: 30, active: true };
const json = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
const parsed = JSON.parse(json);
console.log(parsed.name); // 'Alice'
console.log(typeof json); // 'string'
console.log(typeof parsed); // 'object'stringify gives you a plain string with no methods. parse gives back an object you can actually use.
Key difference
JSON.stringify() builds a string by recursively walking the value, calling toJSON() on objects that have it, and silently skipping anything without a JSON equivalent. JSON.parse() does the reverse but validates every byte against RFC 8259. Single quotes, unquoted keys, trailing commas - all of these throw. The round-trip is not lossless: what stringify drops cannot come back through parse.
When to use
- API requests:
JSON.stringify(body)before sending,response.json()after receiving. - localStorage:
setItem('key', JSON.stringify(data))to save,JSON.parse(getItem('key'))to load. - Debug output:
JSON.stringify(obj, null, 2)for a readable, indented snapshot in logs. - Node.js config:
JSON.parse(fs.readFileSync('config.json', 'utf8'))on app start. - Skip for: Dates, functions, classes, circular references,
BigInt.
What stringify changes or drops
const obj = {
fn: () => 'hello', // dropped from object
undef: undefined, // dropped from object
sym: Symbol('id'), // dropped from object
date: new Date('2023-01-01'), // becomes ISO string
nan: NaN, // becomes null
};
JSON.stringify(obj);
// '{"date":"2023-01-01T00:00:00.000Z","nan":null}'
// In arrays, undefined and functions become null:
JSON.stringify([1, undefined, () => {}, 3]);
// '[1,null,null,3]'
// BigInt throws immediately - no silent conversion:
JSON.stringify({ id: 123n }); // TypeError: Do not know how to serialize a BigIntreplacer and reviver
Both methods have an optional parameter for custom handling. The replacer in stringify filters or transforms values on the way out. The reviver in parse restores types on the way in.
// replacer as array: property whitelist
JSON.stringify({ name: 'Bob', password: 'secret' }, ['name']);
// '{"name":"Bob"}'
// replacer as function: handle BigInt
JSON.stringify({ id: 123n }, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
);
// '{"id":"123"}'
// reviver: restore Date objects after parse
const json = '{"name":"Alice","birthday":"1990-06-15T00:00:00.000Z"}';
const result = JSON.parse(json, (key, value) =>
key === 'birthday' ? new Date(value) : value
);
console.log(result.birthday instanceof Date); // trueThe replacer is called top-down from the root; the reviver is called bottom-up, nested properties first.
How V8 handles this internally
In V8, JSON.stringify() runs through a C++ serializer that walks the object graph, writes primitives directly, and calls toJSON() when present. JSON.parse() is a recursive descent parser that validates UTF-8 and checks syntax per RFC 8259. Both run in native code. That is why they outperform any JavaScript-based JSON library by a wide margin.
Common mistakes
1. Forgetting to parse after reading from localStorage
localStorage.setItem('user', JSON.stringify({ name: 'Alice' }));
// Wrong
const raw = localStorage.getItem('user');
console.log(raw.name); // undefined - raw is a string, not an object
// Fix
const user = JSON.parse(localStorage.getItem('user'));
console.log(user.name); // 'Alice'In production apps this is probably the most common JSON bug. The storage layer always returns strings.
2. Expecting Date to survive the round-trip
const obj = { birthday: new Date('1990-01-01') };
const clone = JSON.parse(JSON.stringify(obj));
console.log(clone.birthday instanceof Date); // false
console.log(clone.birthday); // '1990-01-01T00:00:00.000Z'
// clone.birthday.getFullYear() → TypeError
// Fix: use a reviver
const restored = JSON.parse(JSON.stringify(obj), (k, v) =>
k === 'birthday' ? new Date(v) : v
);
console.log(restored.birthday instanceof Date); // true3. Circular references crash stringify
const obj = { x: 1 };
obj.self = obj;
JSON.stringify(obj); // TypeError: Converting circular structure to JSON
// Fix: replacer with WeakSet
function safeStringify(value) {
const seen = new WeakSet();
return JSON.stringify(value, (key, val) => {
if (typeof val === 'object' && val !== null) {
if (seen.has(val)) return '[Circular]';
seen.add(val);
}
return val;
});
}4. Passing a non-string to JSON.parse()
JSON.parse({ a: 1 }); // SyntaxError
// The object coerces to "[object Object]", which is not valid JSON.
// Always pass an actual JSON string:
JSON.parse('{"a":1}'); // { a: 1 }5. Using JSON for deep clone when you have special types
const original = { date: new Date(), map: new Map([[1, 'one']]) };
const broken = JSON.parse(JSON.stringify(original));
// broken.date is a string, broken.map is {}
// Use structuredClone() instead:
const proper = structuredClone(original);
console.log(proper.date instanceof Date); // true
console.log(proper.map instanceof Map); // trueReal-world usage
- React: persist state to localStorage via
stringifyon change,parseon mount (common in Next.js for theme preferences). - Express:
express.json()middleware auto-parses incoming JSON bodies;res.json()auto-stringifies the response. - Node.js:
fs.writeFileSync('config.json', JSON.stringify(config, null, 2))for human-readable config files. - Fetch API:
body: JSON.stringify(data)on POST requests,await response.json()on the response. - Redux Persist: serializes store slices to localStorage between sessions.
Follow-up questions
Q: What happens to undefined in an object vs. in an array?
A: In an object, the key is dropped entirely: JSON.stringify({a: undefined}) gives '{}'. In an array, undefined becomes null to keep index positions: JSON.stringify([undefined]) gives '[null]'.
Q: replacer accepts both a function and an array. When do you choose the array?
A: Use an array for a quick property whitelist: JSON.stringify(user, ['name', 'email']). Use a function when you need conditional logic or type transformation across nested properties.
Q: What is the difference between JSON.parse() and eval()?
A: eval() executes arbitrary JavaScript, which is dangerous with untrusted data. JSON.parse() only accepts valid JSON and cannot run code. Always use JSON.parse() for data from external sources.
Q: How does JSON.parse() handle duplicate keys in a JSON string?
A: The last value wins. JSON.parse('{"a":1,"a":2}') returns {a: 2}. The spec permits duplicate keys, but producing them is always a bug in the source.
Q: (Senior) Implement a stringify that handles circular references without any libraries.
A: Track visited objects in a WeakSet. In the replacer function, check if the current value is already in the set before adding it. WeakSet is the right choice because it holds references weakly and does not block garbage collection.
Examples
localStorage: save and restore settings
const settings = { theme: 'dark', fontSize: 16, notifications: true };
// Save
localStorage.setItem('settings', JSON.stringify(settings));
// Restore (guard against null - key might not exist yet)
const raw = localStorage.getItem('settings');
const loaded = raw !== null ? JSON.parse(raw) : settings;
console.log(loaded.theme); // 'dark'Always check for null before calling JSON.parse(). localStorage.getItem() returns null for missing keys, and accessing properties on it later will throw.
POST request with Fetch
async function createUser(data) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data) // object → string for the wire
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json(); // parses the JSON response body
}
createUser({ name: 'Alice', role: 'developer' })
.then(user => console.log('Created with id:', user.id));response.json() is essentially JSON.parse(await response.text()). It throws if the response body is not valid JSON, so check response.ok first.
replacer and reviver for edge cases
// Stringify: drop sensitive fields, handle BigInt
const payload = {
userId: 9007199254740993n, // too large for a safe JS number
name: 'Bob',
password: 'secret'
};
const serialized = JSON.stringify(payload, (key, value) => {
if (key === 'password') return undefined; // drop it
if (typeof value === 'bigint') return value.toString(); // convert
return value;
});
// '{"userId":"9007199254740993","name":"Bob"}'
// Parse: restore Date from API response
const apiResponse = '{"id":1,"createdAt":"2023-06-15T10:00:00.000Z"}';
const record = JSON.parse(apiResponse, (key, value) =>
key === 'createdAt' ? new Date(value) : value
);
console.log(record.createdAt instanceof Date); // true
console.log(record.createdAt.getFullYear()); // 2023Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.