← Back to Blog

JSON.stringify vs JSON.parse: Complete Guide with Examples

Every JavaScript developer uses JSON.stringify() and JSON.parse() daily - but most only scratch the surface. This guide covers the full API, real-world patterns, and the surprising edge cases that catch developers off guard.

The Core Problem: JavaScript Objects Are Not JSON

JavaScript objects live in memory as structured data with types, prototypes, and references. JSON is a text format - a flat string with a strict grammar. The moment you need to send data across a network, store it in localStorage, or log it to a file, you need to cross the boundary between the two. That is exactly what these two functions do:

  • JSON.stringify(value) - Serializes a JavaScript value into a JSON string (object → text)
  • JSON.parse(text) - Deserializes a JSON string back into a JavaScript value (text → object)

Getting this wrong causes silent data loss, runtime errors, and security vulnerabilities. Understanding both functions deeply will save you from hours of debugging.

JSON.stringify() in Depth

The full signature is JSON.stringify(value, replacer, space). Most developers only use the first argument, but all three are powerful.

Basic Usage

const user = { id: 1, name: "Alice", role: "admin" };

// Compact (for network transmission)
JSON.stringify(user);
// '{"id":1,"name":"Alice","role":"admin"}'

// Pretty-printed (for logging or human-readable files)
JSON.stringify(user, null, 2);
// {
//   "id": 1,
//   "name": "Alice",
//   "role": "admin"
// }

// Tab-indented
JSON.stringify(user, null, "\t");

The Replacer Parameter: Filter and Transform Output

The second argument to JSON.stringify() is the replacer. It can be an array (allowlist of keys) or a function (transform each value):

// Array replacer - only include these keys
JSON.stringify(user, ["id", "name"]);
// '{"id":1,"name":"Alice"}' - role is excluded

// Function replacer - transform values
JSON.stringify(user, (key, value) => {
  if (key === "role") return undefined; // omit this key
  if (typeof value === "string") return value.toUpperCase();
  return value;
});
// '{"id":1,"name":"ALICE"}'

The function replacer is called with every key-value pair, including nested ones. Returning undefined from the replacer omits that key entirely from the output.

Custom toJSON() Method

Any object can control its own serialization by implementing a toJSON() method. JSON.stringify() calls this automatically if it exists:

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

JSON.stringify({ price: new Money(9.99, "USD") });
// '{"price":"9.99 USD"}'

JSON.parse() in Depth

The full signature is JSON.parse(text, reviver). The reviver is the counterpart to the replacer - it transforms parsed values back into rich JavaScript objects.

Basic Usage

const json = '{"id":1,"name":"Alice","createdAt":"2026-03-26T10:00:00Z"}';
const parsed = JSON.parse(json);
// { id: 1, name: 'Alice', createdAt: '2026-03-26T10:00:00Z' }
// Note: createdAt is a STRING, not a Date object

The Reviver Function: Restore Types

JSON has no native Date type - dates become strings. The reviver function lets you restore them on the way in:

const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

const parsed = JSON.parse(json, (key, value) => {
  if (typeof value === "string" && ISO_DATE.test(value)) {
    return new Date(value);
  }
  return value;
});

parsed.createdAt instanceof Date; // true
parsed.createdAt.getFullYear();   // 2026

Real-World Patterns

Pattern 1: Sending Data to an API

// Serialize before sending
const response = await fetch("/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
});

// Parse the response
const data = await response.json(); // response.json() calls JSON.parse() internally

Pattern 2: LocalStorage (Strings Only)

// Save state to localStorage
localStorage.setItem("cart", JSON.stringify(cartItems));

// Restore state from localStorage
const saved = localStorage.getItem("cart");
const cartItems = saved ? JSON.parse(saved) : [];

Pattern 3: Deep Clone (With Caveats)

// Quick deep clone for plain objects - no references, no functions
const original = { a: 1, nested: { b: 2 } };
const clone = JSON.parse(JSON.stringify(original));

// Mutating clone does NOT affect original
clone.nested.b = 99;
original.nested.b; // still 2

The JSON.parse(JSON.stringify(obj)) deep clone only works for plain data. It drops undefined, functions, Symbols, and non-serializable types. For production cloning, use structuredClone() (available in modern browsers and Node 17+) instead.

Format & Validate Your JSON Instantly

Paste your JSON to format it with 2-space indentation, syntax-highlight it, and catch any parse errors before they hit your app. Free, runs entirely in your browser.

Open JSON Formatter →

Step-by-Step: Debugging a stringify / parse Problem

  1. Check what you are stringifying. Run console.log(typeof value) before calling stringify. If it is already a string, you will double-encode it.
  2. Check for circular references. If you get TypeError: Converting circular structure to JSON, you have an object that references itself.
  3. Check for lost data. If fields disappear after stringify + parse, they probably contained undefined, a function, or a Symbol.
  4. Wrap parse in try/catch. Never trust external JSON strings. Always catch the SyntaxError from malformed input.
  5. Inspect with a formatter. Use our JSON Formatter to pretty-print and visually verify the structure before and after.

Common Pitfalls and How to Fix Them

Pitfall 1: undefined, Functions, and Symbols Are Silently Dropped

const obj = {
  name: "Alice",
  greet: function() { return "hi"; }, // dropped
  id: undefined,                       // dropped
  sym: Symbol("x")                     // dropped
};

JSON.stringify(obj);
// '{"name":"Alice"}' - greet, id, and sym are gone with no error

Fix: if you need to serialize functions or undefined values, use a replacer function to convert them to a string representation first.

Pitfall 2: Date Objects Become Strings

const obj = { ts: new Date("2026-03-26") };
const roundTripped = JSON.parse(JSON.stringify(obj));

roundTripped.ts instanceof Date; // false - it is a string now
typeof roundTripped.ts;          // "string"

Fix: use a reviver function in JSON.parse() to restore Date objects, as shown above.

Pitfall 3: Circular References Throw

const a = {};
a.self = a; // circular reference

JSON.stringify(a);
// TypeError: Converting circular structure to JSON

Fix: use a replacer that tracks seen objects, or use a library like flatted that supports circular JSON.

Pitfall 4: BigInt Throws

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

Fix: use a replacer to convert BigInt to string: (key, val) => typeof val === 'bigint' ? val.toString() : val

Pitfall 5: Double Parsing

// API returns a JSON string that contains JSON (already-serialized)
const raw = '"{\\"name\\":\\"Alice\\"}"'; // a JSON-encoded JSON string

JSON.parse(raw); // returns the string '{"name":"Alice"}'
// You need to parse it TWICE
JSON.parse(JSON.parse(raw)); // returns { name: 'Alice' }

This happens when backends double-serialize data. Always inspect the type of what JSON.parse() returns - if it is still a string, parse again.

Performance Considerations

For most applications, JSON serialization is fast enough to ignore. But for high-throughput APIs or large datasets, keep these in mind:

  • Minify in production: Do not pass a space argument in API responses. The whitespace adds bytes for no machine benefit.
  • Avoid stringify in hot loops: Serializing the same large object thousands of times per second is expensive. Cache the stringified output if the object is immutable.
  • Consider alternatives for extreme performance: Libraries like fast-json-stringify (uses a schema to generate optimized code) can be 2-5x faster than native for known shapes.
  • Stream large payloads: For multi-megabyte JSON, streaming parsers like JSONStream or clarinet avoid loading the entire document into memory.

Quick Reference: What Gets Lost in stringify?

  • undefined values - key is dropped from objects, becomes null in arrays
  • Functions - dropped silently
  • Symbol keys and Symbol values - dropped silently
  • Date objects - converted to ISO string (not restored on parse)
  • BigInt - throws TypeError
  • Circular references - throws TypeError
  • RegExp objects - become {}
  • Map, Set, WeakMap - become {}
  • NaN, Infinity, -Infinity - become null

Frequently Asked Questions

Can JSON.parse throw an error?

Yes. JSON.parse() throws a SyntaxError if the input is not valid JSON. Always wrap it in a try/catch when parsing untrusted or external data. A safe wrapper: function safeParse(s, fallback = null) { try { return JSON.parse(s); } catch { return fallback; } }

Is JSON.stringify(JSON.parse(str)) the same as the original string?

Not necessarily. JSON.stringify(JSON.parse(str)) produces valid, normalized JSON, but key order may differ, extra whitespace is removed, and numbers may be reformatted. It is good for normalization but not for exact round-trip fidelity.

What is the difference between JSON.stringify and toString()?

toString() calls the object's prototype method, which for plain objects returns [object Object] - useless. JSON.stringify() produces a valid JSON string. Always use JSON.stringify() when you need structured text output.

Why does JSON.parse return a string sometimes?

If the input JSON is a quoted string (e.g., '"hello"'), JSON.parse() correctly returns the JavaScript string "hello". This is valid behavior - a JSON document can be any JSON value, not just an object or array.

How do I handle NaN and Infinity in JSON?

JSON does not support NaN or Infinity. JSON.stringify() converts them to null silently. If you need these values, use a replacer to convert them to strings like "NaN" or "Infinity", and a reviver to restore them on parse.

Use our free tool to format, validate, and inspect any JSON string instantly → Open JSON Formatter.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.