Developer Tools

JSON.parse vs JSON.stringify: What's the Difference?

JSON.parse turns a string into a JavaScript object; JSON.stringify turns an object back into a string. Here is the full comparison, the round-trip, and the gotchas an AI summary skips.

By Inventive HQ Team

JSON.parse converts a JSON string into a JavaScript value, and JSON.stringify converts a JavaScript value back into a JSON string — they are exact inverses of each other. You call JSON.stringify on the way out (saving to storage, sending over the network) and JSON.parse on the way in (reading a response, loading a config). Parsing turns text you cannot use directly into a live object; stringifying turns a live object into text you can store or transmit. The two together are the entire bridge between JavaScript's in-memory world and the flat text that travels between systems.

That is the summary an AI overview will give you. The part it can't give you is the set of quiet traps that bite real code: stringify silently deletes your undefined values and functions, turns Date objects into strings you can't turn back, converts NaN to null, and throws on a circular reference or a BigInt. This article shows the full comparison, the round-trip, and each gotcha with the code to handle it.

The round trip in one picture

Think of the two methods as a two-way door between a structured object living in memory and a flat string of text. stringify flattens; parse rebuilds.

The JSON round trip: stringify flattens an object to a string, parse rebuilds it A JavaScript object on the left and a JSON string on the right, connected by a stringify arrow going right and a parse arrow going left, with a packet animating back and forth between them. JSON.stringify ⇄ JSON.parse JavaScript object { name: "Ada", age: 36, tags: ["dev"] } JSON string '{"name":"Ada", "age":36, "tags":["dev"]}' stringify → ← parse Structured, mutable, in-memory · vs · flat text you can store or send

The direction you're moving decides which method you reach for first. Producing JSON (saving, sending)? stringify first. Consuming JSON (reading, loading)? parse first.

JSON.parse vs JSON.stringify: the full comparison

JSON.parseJSON.stringify
DirectionJSON string → JS valueJS value → JSON string
SignatureJSON.parse(text[, reviver])JSON.stringify(value[, replacer[, space]])
InputA string of valid JSONAny JavaScript value
OutputObject, array, number, string, boolean, or nullA string (or undefined for unsupported top-level values)
Optional hookreviver — transform each value on the way inreplacer — filter/transform each value on the way out
Formatting controlNonespace — pretty-print with N spaces or a string
On bad inputThrows SyntaxError immediatelySilently omits undefined/functions; throws on circular refs & BigInt
Typical useReading a fetch response, loading config, localStorage.getItemSending a request body, localStorage.setItem, logging
Which do I use?When you receive text and need an objectWhen you have an object and need text

JSON.parse: string → object

JSON.parse takes JSON text and returns the corresponding JavaScript value. The single most important thing to know: it throws on invalid input, so untrusted or network data belongs in a try/catch.

const obj = JSON.parse('{"name":"Ada","age":36}');
obj.name; // "Ada"  — now a real object you can read and mutate

// Malformed JSON throws — always guard external input
try {
  JSON.parse("{ name: 'Ada' }"); // single quotes + unquoted key = invalid
} catch (err) {
  console.error("Bad JSON:", err.message);
}

The optional reviver is a function called for every key/value pair after parsing, letting you transform values on the way in — most commonly to rebuild Date objects (see the Dates section). Returning undefined from the reviver removes that property from the result.

const data = JSON.parse('{"createdAt":"2026-07-18T00:00:00.000Z"}', (key, value) => {
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value); // turn ISO strings back into Date objects
  }
  return value;
});
data.createdAt instanceof Date; // true
Advertisement

JSON.stringify: object → string

JSON.stringify walks a JavaScript value and returns JSON text. Where parse fails loudly, stringify mostly fails quietly — it drops values it can't represent rather than erroring, which is exactly why the gotchas below catch people.

JSON.stringify({ name: "Ada", age: 36 });
// '{"name":"Ada","age":36}'

// space (3rd arg) pretty-prints — great for files and logs, never for payloads
JSON.stringify({ name: "Ada", age: 36 }, null, 2);
// {
//   "name": "Ada",
//   "age": 36
// }

The optional replacer is either an allow-list array of property names, or a function run for every value on the way out. Returning undefined from a replacer function drops that property.

// Array replacer: keep only these keys
JSON.stringify({ name: "Ada", age: 36, secret: "x" }, ["name", "age"]);
// '{"name":"Ada","age":36}'

// Function replacer: redact a field, double every number
JSON.stringify({ price: 10, token: "abc" }, (key, value) => {
  if (key === "token") return undefined;      // drop it
  if (typeof value === "number") return value * 2;
  return value;
});
// '{"price":20}'
Loading interactive tool...

The gotchas an AI overview skips

This is the part that separates "I know what the two methods do" from "I know why my data came back wrong." All of these are JSON.stringify behaviors, because JSON is a much smaller type system than JavaScript — several JS values have no JSON equivalent, so something has to give.

JavaScript valueWhat JSON.stringify doesWhy
undefined (in object)Property omitted entirelyNot valid JSON
undefined (in array)Replaced with nullArray positions must stay aligned
Function / Symbol valueOmitted (object) or null (array)Not valid JSON
Symbol-keyed propertyIgnored, even by a replacerSymbols aren't serializable
NaN, Infinity, -InfinityBecome nullJSON has no non-finite numbers
new Date()ISO-8601 string via toJSON()JSON has no date type
BigIntThrows TypeErrorIntentional — no safe representation
Circular referenceThrows TypeErrorCan't serialize an infinite loop
Map, SetBecome {} (empty object)No JSON equivalent; not enumerable as props
JSON.stringify({ a: undefined, b: () => {}, c: NaN, d: [undefined, 1] });
// '{"c":null,"d":[null,1]}'   — a and b vanished; NaN → null; array undefined → null

JSON.stringify({ big: 10n });    // TypeError: Do not know how to serialize a BigInt

const node = {};
node.self = node;
JSON.stringify(node);            // TypeError: Converting circular structure to JSON

Dates don't survive a round trip

A Date stringifies to an ISO string (via its built-in toJSON), and parse has no idea it was ever a date. The round trip is lossy unless you rebuild it with a reviver:

const start = { at: new Date("2026-07-18") };
const text = JSON.stringify(start);          // '{"at":"2026-07-18T00:00:00.000Z"}'
const back = JSON.parse(text);
typeof back.at;                              // "string"  — NOT a Date

// Rebuild with a reviver:
const fixed = JSON.parse(text, (k, v) =>
  typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v) ? new Date(v) : v
);
fixed.at instanceof Date;                    // true

Circular references: handle or avoid

For a cyclic graph, either strip the cycle with a WeakSet-based replacer, or reach for the modern built-in structuredClone() when you actually want a copy rather than a string.

function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) return undefined; // drop the second sighting
      seen.add(value);
    }
    return value;
  });
}

The deep-clone shortcut (and its trap)

You'll see JSON.parse(JSON.stringify(obj)) used as a quick deep copy. It genuinely works — but only for plain JSON-safe data. Run any rich object through it and the gotchas above quietly corrupt your copy: Dates turn to strings, undefined and functions disappear, Map/Set empty out, NaN becomes null, and a BigInt throws.

const clone = JSON.parse(JSON.stringify(original)); // fine for nested plain objects/arrays
const faithful = structuredClone(original);         // preserves Dates, Maps, Sets, undefined, etc.

If the object is pure data, the JSON trick is fine and fast. If it holds Dates, Maps, Sets, or undefined you care about, use structuredClone() — it exists precisely for the cases where the JSON round trip lies to you.

When you use each in practice

  • Sending an API requestfetch(url, { body: JSON.stringify(payload) }). Stringify out.
  • Reading an API responseconst data = await res.json() (which parses for you), or JSON.parse(text). Parse in.
  • localStorage / sessionStorage — storage only holds strings: setItem(k, JSON.stringify(v)) to save, JSON.parse(getItem(k)) to read.
  • Config & log filesJSON.parse to load; JSON.stringify(obj, null, 2) to write something a human can read.
  • Pretty-printing for humans — the space argument; use a live JSON formatter to see the difference instantly.

The bottom line

JSON.parse and JSON.stringify are two halves of one bridge: parse brings flat text into JavaScript as a live value, stringify sends a live value back out as text. Remember the direction (produce → stringify, consume → parse), always wrap parse in try/catch because it throws on bad input, and respect the type gap — stringify will quietly drop undefined, functions, and symbols, flatten Dates to strings, turn NaN into null, and throw on BigInt and circular references. Reach for the reviver, replacer, and space parameters when the defaults aren't enough, and reach for structuredClone() when you need a faithful copy rather than a string.

Frequently Asked Questions

What is the difference between JSON.parse and JSON.stringify?

They are inverses of each other. JSON.parse takes a JSON-formatted string and returns a live JavaScript value (object, array, number, etc.) you can read and mutate. JSON.stringify takes a JavaScript value and returns a JSON string you can store or transmit. You stringify on the way out (saving to localStorage, sending over the network) and parse on the way in (reading a response body or a config file). Parse throws a SyntaxError on malformed input; stringify silently drops values it cannot represent.

Does JSON.parse or JSON.stringify come first?

It depends on the direction of your data. When you are producing JSON — saving to disk, sending an HTTP request, writing to localStorage — you call JSON.stringify first to turn your object into text. When you are consuming JSON — reading a response, loading a file, restoring from storage — you call JSON.parse first to turn text back into an object. A full round trip is JSON.parse(JSON.stringify(value)).

Why does JSON.stringify drop my undefined values and functions?

Because undefined, functions, and symbols are not valid JSON — the format has no way to represent them. When stringify meets one inside an object it omits the whole property; when it meets one inside an array it substitutes null to keep positions aligned. NaN and Infinity also become null. If you need those values preserved, convert them to a JSON-safe form yourself using a replacer function.

Why does my Date come back as a string after JSON round-trip?

JSON has no date type. JSON.stringify calls the Date object's built-in toJSON() method, which produces an ISO-8601 string like "2026-07-18T00:00:00.000Z". JSON.parse has no way to know that string was ever a Date, so it returns a plain string. To restore Date objects, pass a reviver function to JSON.parse that detects date-shaped strings and wraps them in new Date(value).

How do I fix a Converting circular structure to JSON error?

That TypeError means an object references itself somewhere in the graph (directly or through a chain), and stringify cannot serialize an infinite loop. Either break the cycle before serializing, only stringify the specific fields you need, or pass a replacer that tracks already-seen objects with a WeakSet and returns undefined the second time it sees one. Modern runtimes also expose structuredClone() for deep-copying objects that stringify cannot handle.

Is JSON.parse(JSON.stringify(obj)) a good way to deep-clone an object?

It works for plain data — nested objects, arrays, strings, numbers, booleans, and null — and is a common quick clone. But it silently corrupts anything JSON cannot represent: Dates become strings, undefined and functions vanish, Map and Set become empty objects, NaN and Infinity become null, and BigInt throws. For a faithful deep copy of rich objects, use structuredClone() instead.

What does the space parameter in JSON.stringify do?

The third argument controls indentation for human-readable output. JSON.stringify(obj, null, 2) pretty-prints with two spaces per level; you can also pass a string like '\t' to indent with tabs. Omit it (or pass 0) and you get compact JSON with no whitespace, which is what you want for network payloads. The whitespace is cosmetic only — it does not change the parsed result.

Does JSON.parse throw an error on invalid JSON?

Yes. Unlike stringify, which fails quietly, JSON.parse throws a SyntaxError the moment it hits malformed input — a trailing comma, single quotes, an unquoted key, or a stray character. Always wrap JSON.parse in try/catch when the input comes from a network, a file, or a user, so one bad payload does not crash your program.

What is a reviver function in JSON.parse?

A reviver is an optional second argument to JSON.parse: a function called for every key/value pair after parsing, letting you transform values on the way in. It is the mirror image of stringify's replacer. Common uses are converting ISO date strings back into Date objects, coercing numeric strings, or renaming keys. Returning undefined from the reviver deletes that property from the result.

JSONJavaScriptdata conversiondevelopment