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 tabsThe 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 firstThe "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 objectThis 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 firstTip: 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.