What Is JSON.stringify?

JSON.stringify converts a JavaScript value into a JSON string. It's the serialization side of the JSON lifecycle — data going into storage, over a network, or into a log.

The syntax:

JSON.stringify(value, replacer, space)

The value argument is required — any JavaScript value (object, array, string, number, boolean, null). The replacer and space arguments are optional.

What gets converted and what gets dropped:

Pretty-Printing with the space Parameter

The third argument controls indentation:

const obj = { name: 'Alice', age: 30 };

// Compact single-line output
JSON.stringify(obj);
// '{"name":"Alice","age":30}'

// Pretty-printed with 2-space indent
JSON.stringify(obj, null, 2);
// {
//   "name": "Alice",
//   "age": 30
// }

// Custom indent string (tab character)
JSON.stringify(obj, null, '\t')
// {
//     "name": "Alice",
//     "age": 30
// }

For visual formatting without writing code, use the free JSON Formatter — paste your JSON and get colorized, indented output instantly.

What Is JSON.parse?

JSON.parse converts a JSON string back into a JavaScript value. It's the deserialization side — data coming from storage, an API response, or localStorage.

The syntax:

JSON.parse(text, reviver)

The text argument is the JSON string to parse. The reviver is an optional function for transforming values during parsing (covered below).

JSON.parse throws a SyntaxError if the input is not valid JSON. This is why you should always wrap it in a try/catch:

try {
  const data = JSON.parse(userInput);
  console.log(data.name);
} catch (err) {
  console.error('Invalid JSON:', err.message);
  // Show user-friendly error, don't silently fail
}

JSON.stringify vs JSON.parse — Side-by-Side

JSON.stringify JSON.parse
Input JavaScript value (object, array, primitive) JSON string (text)
Output JSON string JavaScript value
Direction Serialization Deserialization
Common use Storing data, logging, sending over network Reading API responses, localStorage, config files
Throws on Circular reference, BigInt, function (if in a context that errors) Malformed JSON, invalid syntax
Second param replacer (function or array) reviver (function)
Third param space (indent) None

Edge Cases That Break JSON.stringify

1. BigInt — TypeError

BigInt is not part of the JSON specification. JSON.stringify throws:

const big = BigInt(9007199254740993);
// TypeError: Do not know how to serialize a BigInt

Fix: Convert BigInts to strings before stringifying, or use structuredClone() which handles BigInt natively.

2. Circular References — TypeError

If an object references itself (directly or through a chain), JSON.stringify throws:

const obj = { name: 'Alice' };
obj.self = obj;  // circular!

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

Fix options:

3. undefined — Silently Dropped (Not Always)

This one catches people. undefined behaves differently in objects vs. arrays:

// In objects: property is removed entirely
const obj = { a: 1, b: undefined, c: 3 };
JSON.stringify(obj);  // '{"a":1,"c":3}'  ← b is gone

// In arrays: undefined becomes null
const arr = [1, undefined, 3];
JSON.stringify(arr);  // '[1,null,3]'    ← replaced with null

This matters when you're round-tripping data — properties that were undefined become missing (not undefined), and array positions that were undefined become null.

4. Symbol Keys — Ignored

Symbol-keyed properties are always skipped in serialization, even when using a replacer:

const key = Symbol('id');
const obj = { [key]: 123, name: 'Alice' };
JSON.stringify(obj);  // '{"name":"Alice"}'  ← Symbol key dropped

5. Date Objects — Becomes a String

Dates become ISO strings. They do not survive JSON.parse as Date instances:

const event = { name: 'Launch', date: new Date('2026-06-11T00:00:00Z') };
const json = JSON.stringify(event);
// '{"name":"Launch","date":"2026-06-11T00:00:00.000Z"}'

const parsed = JSON.parse(json);
parsed.date instanceof Date  // false — it's a string!
parsed.date  // "2026-06-11T00:00:00.000Z"

Use the reviver function to restore Date objects after parsing (shown below).

Edge Cases That Break JSON.parse

1. Trailing Commas

JSON does not allow trailing commas. This is the most common JSON parse error in practice:

// This is valid JavaScript — but not valid JSON
const jsObj = { name: 'Alice', age: 30, };
JSON.parse('{ "name": "Alice", "age": 30, }');
// SyntaxError: Unexpected token } in JSON at position 34

// Correct:
JSON.parse('{ "name": "Alice", "age": 30 }');

The JSON Validator catches trailing commas and other syntax errors before they hit your code.

2. Single Quotes

JSON requires double quotes for all strings. Single quotes are invalid:

// SyntaxError: Unexpected token ' in JSON at position 1
JSON.parse("{ 'name': 'Alice' }");

// Correct:
JSON.parse('{ "name": "Alice" }');

3. JavaScript Comments

Comments are valid in JavaScript objects but not in JSON:

// SyntaxError: Unexpected token / in JSON at position 12
JSON.parse('{ "name": "Alice" // this is a comment }');

// Also invalid:
JSON.parse('{ "name": "Alice" /* block comment */ }');

This is why you can't just copy a JavaScript object literal as JSON — you need to remove comments and double-quote all keys and string values.

4. Unquoted Keys

JavaScript object literals allow bare keys; JSON requires all keys to be double-quoted:

// SyntaxError: Unexpected token n in JSON at position 2
JSON.parse('{ name: "Alice" }');

// Correct:
JSON.parse('{ "name": "Alice" }');
Golden rule: Always wrap JSON.parse in a try/catch. A malformed JSON string from an API response or user input will crash your code without one. And validate before parsing — use the JSON Validator to catch errors before they reach JSON.parse.

The Replacer Function

The second argument to JSON.stringify can be a function or an array. It gives you fine-grained control over what gets serialized and how.

Replacer as an Array — Whitelist Properties

const user = {
  id: 42,
  name: 'Alice',
  email: 'alice@example.com',
  passwordHash: '3f1a9...',
  createdAt: '2026-06-11'
};

// Only include name and id — drop email, password, etc.
const safe = JSON.stringify(user, ['id', 'name']);
// '{"id":42,"name":"Alice"}'

This is the simplest way to exclude sensitive fields from a serialized payload.

Replacer as a Function — Transform or Skip

const user = {
  name: 'Alice',
  email: 'alice@example.com',
  createdAt: new Date('2026-06-11'),
  role: 'admin'
};

// Transform role values (security masking)
const safe = JSON.stringify(user, (key, value) => {
  if (key === 'email') return '[REDACTED]';
  if (key === 'role') return 'user';  // downgrade role for logging
  return value;
});
// '{"name":"Alice","email":"[REDACTED]","createdAt":"2026-06-11T00:00:00.000Z","role":"user"}'

The replacer is called for every key-value pair. A few gotchas:

The Reviver Function

The second argument to JSON.parse transforms values during parsing. It's called for every key-value pair with the parsed JSON, allowing you to restore types that JSON doesn't natively support.

Restoring Date Objects

const json = '{"name":"Launch","date":"2026-06-11T00:00:00.000Z"}';

const parsed = JSON.parse(json, (key, value) => {
  // Detect ISO 8601 date strings and convert to Date
  if (
    typeof value === 'string' &&
    /^\\d{4}-\\d{2}-\\d{2}T/.test(value)
  ) {
    return new Date(value);
  }
  return value;
});

parsed.date instanceof Date  // true!

Sanitization with Reviver

// Strip HTML tags from string values
const dirty = '{"bio":"Hello"}';

const clean = JSON.parse(dirty, (key, value) => {
  if (typeof value === 'string') {
    return value.replace(/<[^>]*>/g, '');
  }
  return value;
});
// { bio: 'Hello' }

Providing Default Values

const partial = '{"name":"Alice"}';  // missing age and role

const withDefaults = JSON.parse(partial, (key, value) => {
  if (key !== '' && value === undefined) return null;  // missing fields → null
  return value;
});
// { name: 'Alice', age: null, role: null }

Common Real-World Patterns

Deep Cloning (and Its Limits)

JSON.parse(JSON.stringify(obj)) is a classic deep clone pattern:

const original = { name: 'Alice', scores: [95, 87, 91] };
const clone = JSON.parse(JSON.stringify(original));

clone.scores.push(99);
original.scores  // [95, 87, 91] — unchanged (deep clone worked)
clone.name        // 'Alice'

What gets lost in this clone:

For most modern use cases, use structuredClone(obj) instead:

// built-in since 2021, handles BigInt, Dates, Maps, Sets, circular refs
const clone = structuredClone(original);

localStorage

localStorage only stores strings — JSON.stringify/parse is the standard pattern:

// Save
const userPrefs = { theme: 'dark', language: 'en', notifications: true };
localStorage.setItem('prefs', JSON.stringify(userPrefs));

// Load
const stored = localStorage.getItem('prefs');
if (stored) {
  const prefs = JSON.parse(stored);  // always try/catch in production
}

API Response Handling

async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  // Always parse JSON inside try/catch — network responses are untrusted
  let data;
  try {
    data = await res.json();  // .json() is just JSON.parse under the hood
  } catch (err) {
    throw new Error('Invalid JSON response from API');
  }

  // Validate structure before using
  if (!data || typeof data !== 'object') {
    throw new Error('Unexpected response format');
  }
  return data;
}

Performance Considerations

Concern Details
Large payloads (>1MB) JSON.parse blocks the main thread. For large payloads in a browser, consider Web Workers to parse off the main thread without freezing the UI.
Hot paths / tight loops Profile first. For 100K+ serialize/parse cycles, JSON is rarely the bottleneck — but microbenchmarks reveal the truth. JSON.parse is faster than eval() or custom parsers for most inputs.
Streaming Both methods are synchronous and work on the full input at once. For streaming JSON, use ReadableStream + JSON.parse incrementally, or libraries like oboe.js that support progressive parsing.
Memory For deep objects, JSON creates intermediate strings before the GC cleans them up. StructuredClone avoids this intermediate string and can be more memory-efficient for very large objects.
Modern alternative: If you're using JSON.parse(JSON.stringify(obj)) purely for deep cloning, replace it with structuredClone(obj). It's built into all modern browsers and Node.js 17+, handles BigInt and circular references correctly, preserves Date type (unlike JSON), and is faster for large objects.

Need to format, validate, or compare JSON without writing code? Our free tools do it all in-browser — no signup, no data sent to any server.

Open JSON Formatter

Frequently Asked Questions

What is the difference between JSON.stringify and JSON.parse?
JSON.stringify converts a JavaScript value to a JSON string. JSON.parse converts a JSON string back to a JavaScript value. They are inverse operations: stringify is serialization, parse is deserialization.
Does JSON.stringify handle undefined values?
JSON.stringify drops undefined values in objects (skips the property entirely), but converts undefined to null in arrays. Functions and Symbols are always silently dropped from both objects and arrays. BigInt throws a TypeError.
How do you handle circular references in JSON.stringify?
JSON.stringify throws a TypeError when it encounters a circular reference. Solutions: use a replacer array to whitelist only safe properties (e.g., JSON.stringify(obj, ['name', 'age'])), use a WeakMap replacer function to replace circulars with a placeholder like '[Circular]', or use the flatted library which handles circular references natively.
Can you use JSON.parse to deep clone an object?
Yes — const copy = JSON.parse(JSON.stringify(obj)) is a common deep clone pattern. However, it loses undefined, functions, Symbols, and BigInt values, and fails on circular references. For most use cases, structuredClone(obj) is a better built-in alternative that handles BigInt and circular references correctly, and preserves Date types.
What does the replacer parameter in JSON.stringify do?
The replacer is a second argument to JSON.stringify: a function (key, value) => newValue or an array of allowed property names. As a function, it is called for every key-value pair including the root (where key is an empty string). Return undefined to skip a property, or return a new value to transform it. As an array, only the listed property names are included.
How do you pretty print JSON with JSON.stringify?
Pass a third argument (the space parameter) to JSON.stringify. Use an integer for spaces (e.g., 2 for 2-space indentation) or a string for a custom indent (e.g., '\t' for a tab). Example: JSON.stringify(obj, null, 2). For visual formatting without writing code, use the free JSON Formatter.
What is the difference between JSON.parse and eval()?
JSON.parse is safe and fast. eval() executes arbitrary JavaScript code, which can run malicious scripts if the input is from an untrusted source. JSON.parse only parses JSON text and throws on any invalid syntax. Always use JSON.parse for parsing JSON data; never use eval() for this purpose.
Does JSON.stringify preserve object key order?
JSON.stringify preserves the enumeration order of object keys in most JavaScript engines (V8, SpiderMonkey). Array indices are always serialized first in ascending numeric order, followed by keys in their insertion order. Map and Set properties are not serialized at all.
How do you handle JSON.parse errors?
Always wrap JSON.parse in a try/catch block. JSON.parse throws a SyntaxError on malformed input (trailing commas, single quotes, comments, unquoted keys). Handle the error gracefully: show a user-friendly message, fall back to a default value, or reject a Promise. The JSON Validator catches parse errors before they reach JSON.parse.
What is structuredClone and when should you use it instead of JSON.parse(JSON.stringify())?
structuredClone() is a built-in browser/Node.js API (added 2021) that deep clones JavaScript values including BigInt, Date objects (preserving type), RegExp, Map, Set, TypedArrays, and circular references. Use it instead of JSON.parse(JSON.stringify()) whenever you need to clone values that contain BigInt, functions, Symbols, or circular references, or when you need Date objects to survive the clone.