Back to Blog
·7 min read

JSON.stringify() Explained: A Practical Guide for JavaScript Developers

Everything you need to know about JSON.stringify() — formatting options, what gets silently dropped, and the gotchas that trip up even experienced developers.

JSON.stringify() looks like a one-line utility, but it has more nuance than most developers realize — from pretty-printing options to silent data loss to the dreaded "double-stringified" string. This guide covers what you actually need to know to use it confidently and debug it when it surprises you.

The basics

JSON.stringify() converts a JavaScript value into a JSON string. In its simplest form, it takes one argument — the value to serialize.

JSON.stringify({ name: "Alice", age: 30 })
// '{"name":"Alice","age":30}'

Pretty-printing with the space argument

The often-overlooked third argument controls indentation, turning a compact one-liner into readable, formatted output — extremely useful for logging and debugging.

JSON.stringify({ name: "Alice", age: 30 }, null, 2)
// '{\n  "name": "Alice",\n  "age": 30\n}'

// Renders as:
// {
//   "name": "Alice",
//   "age": 30
// }

// You can also pass a string instead of a number:
JSON.stringify(data, null, '\t')   // indent with tabs

The replacer argument: filtering and transforming

The second argument — the replacer — lets you control exactly what gets included in the output. Pass an array of key names to allow-list specific fields, or a function to transform values during serialization.

const user = { name: "Alice", password: "secret123", age: 30 }

// Array form — only include these keys
JSON.stringify(user, ["name", "age"])
// '{"name":"Alice","age":30}'

// Function form — redact sensitive fields
JSON.stringify(user, (key, value) => key === "password" ? undefined : value)
// '{"name":"Alice","age":30}'

Tip: Use the function-form replacer to redact sensitive fields (passwords, tokens, secrets) before logging objects — it's far safer than remembering to manually strip them every time.

What gets silently dropped

This is the part that surprises people most: JSON.stringify() doesn't throw an error for unsupported values — it silently omits or transforms them.

JSON.stringify({
  name: "Alice",
  greet: function() { return "hi" },  // omitted entirely
  id: undefined,                       // omitted entirely
  tag: Symbol("x"),                    // omitted entirely
  when: new Date("2026-01-01"),        // converted to ISO string
  big: 10n                             // throws TypeError!
})
// '{"name":"Alice","when":"2026-01-01T00:00:00.000Z"}'
// — functions, undefined, and symbols vanish without warning
// — BigInt values throw a TypeError and must be converted to string or Number first

The "double-stringified" trap

A surprisingly common bug: calling JSON.stringify() on data that has already been stringified, producing a string that contains an escaped JSON string inside it.

const payload = JSON.stringify({ name: "Alice" })
// payload is now the STRING '{"name":"Alice"}'

const doubled = JSON.stringify(payload)
// '"{\"name\":\"Alice\"}"'  — a string containing escaped JSON!

// The fix: don't stringify a value that's already a string
JSON.parse(doubled)        // '{"name":"Alice"}' — still a string, just unescaped
JSON.parse(JSON.parse(doubled))  // { name: "Alice" } — finally the object

This typically happens when a value is stringified once when stored (e.g., in a database TEXT column or a log message) and then accidentally stringified again before being sent or displayed. If you ever see escaped quotes (\") inside a JSON string in your logs or API responses, this is almost certainly the cause.

JSON Stringify Tool

See exactly what JSON.stringify() produces for your data, and use reverse mode to untangle doubly-stringified strings.

JSON Formatter

Quickly pretty-print any JSON string for readability — handy after parsing a stringified payload.

Circular references

JSON.stringify() throws a TypeError ("Converting circular structure to JSON") if the object contains a reference back to itself, directly or indirectly. This commonly happens with DOM nodes, ORM model instances with parent/child relationships, or manually-linked data structures.

const a = { name: "a" }
const b = { name: "b", parent: a }
a.child = b

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

// Fix: use a replacer that tracks seen objects, or strip the circular reference first

Tip: JSON.stringify() and JSON.parse() are exact inverses for plain data — but only for values JSON actually supports. Round-tripping a Date gives you back a string, not a Date object, which is a frequent source of subtle bugs.

Ad