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 input
  • JSON.parse accepts a string; passing an object first requires String(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 undefined deletes 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]
ValueResult of stringifyWhy
undefinedomitted in objects, null in arraysno representation
functionomittednot data
NaN, Infinitynullnot representable as numbers
DateISO 8601 stringDate.prototype.toJSON
Map, Set{}no own enumerable properties
BigIntTypeErrorwould lose precision silently
circular referenceTypeErrorcannot be serialised
symbol-keyed propertyomittedno 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.

Syntax and types JSON in HTTP APIs

Last refreshed 2026-09-18.