parse, stringify, revivers and replacers
Convert between text and data safely, transform values during parsing, and know exactly which JavaScript values cannot survive the trip.
Parsing safely
const data = JSON.parse(text);
// server responses are still untrusted input
function safeParse(text, fallback = null) {
try {
return JSON.parse(text);
} catch (err) {
console.warn('invalid JSON:', err.message);
return fallback;
}
}
JSON.parse('{"a":1}'); // ok
JSON.parse('{a:1}'); // SyntaxError: keys must be quoted
JSON.parse('undefined'); // SyntaxError: not a value
JSON.parse(''); // SyntaxError: unexpected end of inputJSON.parseaccepts a string; passing an object first requiresString(x)and is usually a bug in the caller.- The error message includes the position of the failure, which is what makes a stray comma findable in a large payload.
- An empty response body throws
Unexpected end of JSON input; a 204 or 304 has no body to parse. - Parsing huge payloads costs real time: on a slow client, splitting a very large response into pages is cheaper than parsing it twice.
⚠️
Never pass untrusted text to
eval as a fallback for JSON.parse. eval executes whatever it is given, and the entire value of JSON is that it is inert data.Revivers: transforming during parse
const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
const dto = JSON.parse(text, (key, value) => {
if (typeof value === 'string' && iso.test(value)) return new Date(value);
if (key === 'total') return Number(value) || 0;
if (key === '__proto__') return undefined; // drop prototype-polluting keys
return value;
});
dto.createdAt instanceof Date; // true- The reviver runs bottom-up: a child value is converted before its parent sees it.
- Returning
undefineddeletes the property from the result. - The reviver is also called for the root value under the key
"". - It can change values but not keys, so renaming fields belongs in an explicit mapping step.
stringify, replacers and formatting
const user = {
id: 7,
name: 'Ada',
createdAt: new Date('2026-09-18T10:00:00Z'),
avatar: undefined,
toJSON() { return { id: this.id, name: this.name }; }
};
JSON.stringify(user); // Date has toJSON(); undefined is dropped
JSON.stringify(user, null, 2); // pretty printed with two spaces
JSON.stringify(user, ['id', 'name']); // allow-list of keys
JSON.stringify(user, (k, v) => (k === 'id' ? undefined : v)); // drop one key
JSON.stringify({ a: undefined, b: null, c: () => {} }); // {"b":null}
JSON.stringify([undefined, 1]); // [null,1]| Value | Result of stringify | Why |
|---|---|---|
undefined | omitted in objects, null in arrays | no representation |
| function | omitted | not data |
NaN, Infinity | null | not representable as numbers |
Date | ISO 8601 string | Date.prototype.toJSON |
Map, Set | {} | no own enumerable properties |
BigInt | TypeError | would lose precision silently |
| circular reference | TypeError | cannot be serialised |
| symbol-keyed property | omitted | no string name to emit |
FAQ
Can I use JSON to copy an object?
It works until it does not: dates become strings,
Map and Set become empty objects, undefined and functions disappear, and a circular reference throws. structuredClone(x) is the correct tool.Why does my data look different after a round trip?
Anything without a JSON representation has already been dropped or converted, numbers may lose the distinction between
1 and 1.0, and key order follows insertion order for the keys that survive.Related
Syntax and types JSON in HTTP APIs
Last refreshed 2026-09-18.