Working with JSON in JavaScript

Published 2026-04-12 · 9 min read

JSON and JavaScript are close cousins: JSON is a strict subset of the object literal syntax, and every modern JavaScript runtime ships with built-in JSON.parse and JSON.stringify. In this article we cover the API, the common pitfalls, and the TypeScript patterns that make working with JSON pleasant.

JSON.parse and JSON.stringify

const text = '{"id": 42, "name": "Ada"}';
const user = JSON.parse(text);
user.name; // "Ada"

const back = JSON.stringify(user);
// '{"id":42,"name":"Ada"}'

const pretty = JSON.stringify(user, null, 2);
/* {
  "id": 42,
  "name": "Ada"
} */

That's 95% of what you need. The third argument to JSON.stringify controls indentation: a number sets the number of spaces, "\t" uses tabs.

The reviver and replacer functions

Both functions accept an optional transform callback. The reviver runs on every parsed value and can return a replacement — useful for rehydrating dates:

const parsed = JSON.parse(text, (key, value) => {
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

The replacer in JSON.stringify works the other direction — filter keys or transform values before they hit the string:

JSON.stringify(user, (key, value) => {
  if (key === "password") return undefined; // drop the key
  return value;
});

Common pitfalls

Dates become strings

JSON.stringify(new Date())returns an ISO-8601 string, and the reverse doesn't happen automatically — you get a string back, not a Date. Use a reviver or a runtime-validated parse to rehydrate them.

undefined disappears

JSON has no undefined. Properties whose values are undefined are silently dropped from the output; values that are undefined inside arrays become null.

JSON.stringify({ a: 1, b: undefined });    // '{"a":1}'
JSON.stringify([1, undefined, 3]);         // '[1,null,3]'

BigInts throw

JSON.stringify(1n) throws TypeError. If you work with large integers — IDs that exceed Number.MAX_SAFE_INTEGER, for example — you have two options: encode as strings on both ends, or install a library like json-bigint.

Circular references throw

JSON.stringifythrows when it encounters a reference cycle. If you're logging complex objects, wrap the call in a try/catch or use a cycle-safe stringifier.

Typing JSON in TypeScript

JSON.parse returns any, which defeats the point of TypeScript. Three approaches are common:

  • Type assertion: const u = JSON.parse(text) as User. Fast but unsafe.
  • Runtime validation: libraries like Zod, Valibot, or Ajv verify the shape matches and narrow the type.
  • Generated types from JSON: our JSON to TypeScript converter infers interface definitions from a sample JSON document.

Structured clone

A common idiom is JSON.parse(JSON.stringify(obj)) to deep-clone an object. Modern runtimes have a better option: structuredClone(obj), which handles dates, maps, sets, and typed arrays correctly. Save JSON-based cloning for when you specifically want the drop-undefined / drop-function behavior.

When it breaks, debug with a tool

If JSON.parse throws, the error message gives you a position but not a line and column. Paste the offending string into the JSON Validator and it will show the exact location, along with a plain-English description of what's wrong.

More from the blog