JavaScriptJSON (parse, stringify)

JSON

JSON — JavaScript Object Notation — is the universal data format for the web. It is a small, strict subset of JavaScript literal syntax that any language can parse. The built-in JSON global exposes just two methods: JSON.stringify turns a value into a JSON string, JSON.parse turns a JSON string back into a value. They are simple on the surface, but each has knobs worth knowing.

The basics

JS
const user = { name: "Ada", age: 36, isAdmin: false, tags: ["math", "code"] };

const json = JSON.stringify(user);
console.log(json);

const parsed = JSON.parse(json);
console.log(parsed.name);   // 'Ada'
{"name":"Ada","age":36,"isAdmin":false,"tags":["math","code"]}
Pretty-printing

The third argument of stringify controls indentation. Pass a number for that many spaces, or a string like "\ " for tabs.

JS
console.log(JSON.stringify(user, null, 2));
{
  "name": "Ada",
  "age": 36,
  "isAdmin": false,
  "tags": [
    "math",
    "code"
  ]
}
The replacer — filter or transform on write

The second argument of stringify is a replacer. It can be an array of keys to keep, or a function that visits every key/value pair.

Array replacer — allowlist

JS
const user = { id: 1, name: "Ada", password: "secret" };
console.log(JSON.stringify(user, ["id", "name"]));
// {"id":1,"name":"Ada"}

Function replacer — transform

JS
const data = { name: "Ada", password: "secret", visits: 0 };

const safe = JSON.stringify(data, (key, value) => {
  if (key === "password") return undefined;   // omit
  if (typeof value === "number") return value + 1;
  return value;
}, 2);

console.log(safe);
{
  "name": "Ada",
  "visits": 1
}

The function is called once with an empty key for the root value, then once per property in a depth-first walk. Returning undefined from a function replacer omits that property entirely.

The reviver — transform on read

JSON.parse accepts a reviver as its second argument. It mirrors the replacer: called for every key/value pair, with the chance to return a replacement.

JS
const json = '{"name":"Ada","born":"1815-12-10"}';

const user = JSON.parse(json, (key, value) => {
  if (key === "born") return new Date(value);
  return value;
});

console.log(user.born instanceof Date);    // true
console.log(user.born.getFullYear());      // 1815
Gotchas — what JSON cannot represent

JSON is much smaller than JavaScript. Several values either disappear or come back wrong after a stringify/parse round trip.

  • Functions — omitted entirely. JSON has no concept of functions.

  • undefined — properties with undefined values are omitted. In arrays they become null.

  • Symbols — keyed by symbol? Omitted. Value is a symbol? Omitted.

  • NaN, Infinity, -Infinity — serialised as null. Information lost.

  • Dates — serialised by calling toJSON() (an ISO string). On parse they stay strings unless you revive them.

  • Map, Set, typed arrays — serialised as empty objects {} (no custom handling).

  • BigInt — throws a TypeError on stringify.

  • Circular references — throw TypeError: Converting circular structure to JSON.

JS
const tricky = {
  fn: () => 1,
  miss: undefined,
  notNumber: NaN,
  when: new Date("2024-01-01"),
  list: [1, undefined, 3],
};

console.log(JSON.stringify(tricky));
{"notNumber":null,"when":"2024-01-01T00:00:00.000Z","list":[1,null,3]}
toJSON — customise serialisation

If an object has a method called toJSON, JSON.stringify uses its return value instead. Date ships with one; you can add your own.

JS
class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }
  toJSON() {
    return { display: `${this.amount} ${this.currency}` };
  }
}

console.log(JSON.stringify({ price: new Money(42, "GBP") }));
// {"price":{"display":"42 GBP"}}
Parsing safely

JSON.parse throws SyntaxError on invalid input. For user-supplied data, wrap it in try/catch rather than trusting the format.

JS
function safeParse(text, fallback = null) {
  try {
    return JSON.parse(text);
  } catch {
    return fallback;
  }
}

console.log(safeParse('{"ok":true}'));      // { ok: true }
console.log(safeParse('not json', {}));     // {}
Never use eval on JSON
Early code sometimes used `eval` to "parse" JSON. It runs arbitrary code — a serious security risk. `JSON.parse` only accepts the strict format and never executes anything.
JSON-safe data
If you want round-trip safety, stick to objects, arrays, strings, finite numbers, booleans, and `null`. Everything else needs a custom `toJSON` and a matching reviver.
One sentence
Think of `JSON.stringify` and `JSON.parse` as a strict subset converter — anything outside that subset (functions, `undefined`, dates, BigInt, cycles) needs you to spell out the rules.