JSON.stringify()

ES5+

Converts a JavaScript value into its JSON text representation, optionally filtering properties with a replacer and pretty-printing with the space parameter. Values JSON cannot represent — functions, undefined, Symbols — are dropped or nulled according to fixed rules, and objects with a toJSON() method serialize as whatever that method returns.

Syntax

JSON.stringify(value, replacer, space)

Parameters

value any

The value to convert to a JSON string

replacer Function | Array optional

A function that alters the behavior of the stringification process, or an array of property names to include

space number | string optional

A string or number used to insert white space into the output JSON string for readability

Return Value

string

A JSON string representing the given value

Examples

JavaScript
const obj = { name: 'John', age: 30 };
console.log(JSON.stringify(obj));
Output:
// '{"name":"John","age":30}'

📌 When to Use

Use JSON.stringify() at the mirror-image boundaries of JSON.parse(): building request bodies for fetch (paired with a Content-Type: application/json header), persisting state to localStorage, emitting structured logs, seeding server-rendered pages with initial data, and generating cache keys or quick fingerprints from small plain objects. The space parameter turns it into a formatter — JSON.stringify(obj, null, 2) is the standard way to write human-readable config files and debug dumps — while the replacer parameter (covered in depth on its own page) filters or transforms values on the way out. Know its serialization rules before trusting it: undefined, functions, and Symbols vanish from objects but become null inside arrays; Date instances become ISO strings via their built-in toJSON(); Map, Set, RegExp, and Error serialize as empty or nearly empty objects; NaN and Infinity become null; and BigInt throws a TypeError. It doubles as a quick deep-clone hack for JSON-safe data, but structuredClone() is now the correct tool for cloning — it preserves Dates, Maps, Sets, and circular structures that the JSON round trip mangles. For anything beyond plain data trees, consider whether a custom toJSON() method should define the canonical wire format instead.

⚠️ Common Mistakes

Using stringify output for deep equality or memo keys without stable key order - key order follows property insertion order, so { a: 1, b: 2 } and { b: 2, a: 1 } stringify differently despite being semantically equal. Sort keys via a replacer, or compare with a real deep-equal function, before treating strings as identity.

Stringifying BigInt crashes - JSON.stringify({ id: 10n }) throws "Do not know how to serialize a BigInt" rather than degrading gracefully. Convert BigInts to strings in a replacer (or a toJSON patch) and agree with the backend on that convention up front.

Expecting Maps and Sets to serialize their contents - both produce "{}" because their data is not stored as enumerable own properties. Spread them first ([...mySet], Object.fromEntries(myMap)) or write a replacer that converts them explicitly.

Attempting to stringify objects with circular references throws an error.

Functions, undefined, and Symbols are silently omitted from the output.

Date objects are converted to ISO strings, not preserved as Date instances.

✅ Best Practices

Wrap stringify in try-catch when input may contain circular references or BigInts - serialization failures are runtime TypeErrors, and logging code especially must never crash the thing it is observing. A cycle-safe replacer or a fallback formatter keeps logs flowing when a weird object arrives.

Prefer structuredClone(obj) over JSON.parse(JSON.stringify(obj)) for cloning - it is faster on large graphs and correctly preserves Dates, Maps, Sets, typed arrays, and circular references, all of which the JSON round trip corrupts, drops, or throws on.

Define toJSON() on domain classes so every serialization site emits one canonical shape - centralizing the wire format beats sprinkling replacer logic across call sites, and it keeps password-like fields excluded by default rather than by memory.

Use the replacer to filter sensitive data like passwords before sending to logs or APIs.

Use the space parameter (2 or 4) for readable debug output, but omit it for production data transfer.

⚡ Performance Notes

JSON.stringify() is native and fast, but it is a full O(n) traversal of the value graph, and it produces one contiguous string — for multi-megabyte states both the traversal and the resulting allocation can stall the main thread, so serialize large payloads in a Web Worker or persist incrementally. The space parameter costs extra time and substantially inflates output size because indentation is repeated on every line; pretty-print for humans only, never for network transfer. A function replacer adds one call per key visited — keep its logic cheap — and toJSON() methods likewise run once per object encountered. A common anti-pattern to avoid: stringifying an object on every render or store update just to compare snapshots; track dirty flags or use a purpose-built deep-equal instead. When generating cache keys, stringify small stable subsets of data rather than whole state trees, and sort keys so logically equal objects produce equal strings.

🌍 Real World Example

Secure Data Serializer

This secureStringify() helper shows the two optional parameters working together in a logging context. The replacer redacts any key on the sensitive list — passwords and tokens leave the process as "[REDACTED]" rather than plaintext — and converts BigInt values to strings so they cannot crash serialization mid-log. The pretty flag switches between compact wire output and two-space indented output for humans. The same options-object pattern extends naturally to truncating long strings or dropping binary fields, the everyday hygiene of production log pipelines.

function secureStringify(obj, options = {}) {
  const { sensitiveKeys = ['password', 'token', 'secret'], pretty = false } = options;

  const replacer = (key, value) => {
    // Filter sensitive keys
    if (sensitiveKeys.includes(key.toLowerCase())) {
      return '[REDACTED]';
    }
    // Handle BigInt
    if (typeof value === 'bigint') {
      return value.toString() + 'n';
    }
    return value;
  };

  return JSON.stringify(obj, replacer, pretty ? 2 : undefined);
}

const userData = {
  name: 'John',
  email: 'john@example.com',
  password: 'secret123',
  apiToken: 'abc123xyz'
};

console.log(secureStringify(userData, { pretty: true }));
// { "name": "John", "email": "john@example.com", "password": "[REDACTED]", "apiToken": "[REDACTED]" }

Related Methods