JavaScript JSON Guide: JSON.parse(), JSON.stringify(), and Real-World Patterns
JSON was born from JavaScript, and working with it in JS is as natural as it gets. But there are nuances — lost data during serialization, security pitfalls, performance traps — that catch even experienced developers. This guide covers everything from basics to production patterns.
JSON.parse(): String to Object
JSON.parse() takes a JSON string and returns a JavaScript value (object, array, string, number, boolean, or null).
// Basic parsing
const user = JSON.parse('{"name": "Alice", "age": 30}');
console.log(user.name); // "Alice"
// Parsing an array
const items = JSON.parse('[1, 2, 3]');
console.log(items[0]); // 1
// Parsing primitives
JSON.parse('"hello"'); // "hello"
JSON.parse('42'); // 42
JSON.parse('true'); // true
JSON.parse('null'); // nullThe reviver parameter
JSON.parse() accepts an optional second argument — a function that transforms each value during parsing. This is essential for handling dates:
// Problem: dates come back as strings
const data = JSON.parse('{"created": "2026-04-27T10:00:00Z"}');
console.log(typeof data.created); // "string" — not a Date!
// Solution: use a reviver to convert date strings
const dateReviver = (key, value) => {
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
};
const data2 = JSON.parse('{"created": "2026-04-27T10:00:00Z"}', dateReviver);
console.log(data2.created instanceof Date); // trueJSON.stringify(): Object to String
JSON.stringify() converts a JavaScript value to a JSON string. It takes three arguments:
JSON.stringify(value, replacer, space)
// ↑ ↑ ↑
// the data filter indentationBasic usage
const user = { name: "Alice", age: 30, active: true };
// Compact (for APIs and storage)
JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
// Pretty printed (for debugging and config files)
JSON.stringify(user, null, 2);
// {
// "name": "Alice",
// "age": 30,
// "active": true
// }The replacer parameter
The replacer filters or transforms values during serialization:
const user = {
name: "Alice",
password: "secret123",
email: "alice@example.com",
age: 30
};
// Array replacer: only include specific keys
JSON.stringify(user, ["name", "email"], 2);
// { "name": "Alice", "email": "alice@example.com" }
// Function replacer: transform or exclude values
JSON.stringify(user, (key, value) => {
if (key === "password") return undefined; // exclude
if (key === "name") return value.toUpperCase(); // transform
return value;
}, 2);
// { "name": "ALICE", "email": "alice@example.com", "age": 30 }Tip: Need to quickly format or minify JSON output? Paste it into our JSON Formatter — it handles pretty printing, minification, and key sorting without writing code.
What JSON.stringify() Silently Drops
This is where most bugs come from. JSON.stringify() silently ignores or converts certain values:
const data = {
fn: function() {}, // ❌ dropped (functions)
sym: Symbol("id"), // ❌ dropped (symbols)
undef: undefined, // ❌ dropped (undefined)
nan: NaN, // ⚠️ becomes null
inf: Infinity, // ⚠️ becomes null
negInf: -Infinity, // ⚠️ becomes null
date: new Date(), // ⚠️ becomes ISO string (can't parse back)
regex: /abc/g, // ⚠️ becomes {}
map: new Map([["a", 1]]), // ⚠️ becomes {}
set: new Set([1, 2, 3]), // ⚠️ becomes {}
name: "Alice", // ✅ kept
count: 42, // ✅ kept
};
console.log(JSON.stringify(data, null, 2));
// Only "name", "count", "nan" (null), "inf" (null), "negInf" (null),
// "date" (string), "regex" ({}), "map" ({}), "set" ({}) surviveKey takeaway: if you serialize and then parse, you won't get the same object back. Dates become strings, Maps/Sets become empty objects, and functions/symbols/undefined disappear entirely.
Getting parse errors? Paste the raw JSON into our JSON Validator to see the exact error location and fix suggestions. Common issues: trailing commas, single quotes, and unescaped characters.
Fetching JSON from APIs
The fetch() API is the modern way to make HTTP requests. Its .json() method parses the response body:
// GET request
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json(); // parses JSON body
}
// POST request with JSON body
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}Defensive fetch wrapper
async function fetchJson(url, options = {}) {
const response = await fetch(url, options);
// Check content type before parsing
const contentType = response.headers.get("content-type");
if (!contentType?.includes("application/json")) {
throw new Error(
`Expected JSON, got ${contentType}. Status: ${response.status}`
);
}
const data = await response.json();
if (!response.ok) {
throw Object.assign(new Error(data.message || "Request failed"), {
status: response.status,
data,
});
}
return data;
}JSON and localStorage
localStorage only stores strings. You must serialize to JSON when writing and parse when reading:
// Save to localStorage
const settings = { theme: "dark", fontSize: 16, sidebar: true };
localStorage.setItem("settings", JSON.stringify(settings));
// Read from localStorage
function getSettings() {
const raw = localStorage.getItem("settings");
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
// Corrupted data — clear it
localStorage.removeItem("settings");
return null;
}
}
// Common mistake: forgetting to parse
const bad = localStorage.getItem("settings");
console.log(bad.theme); // undefined — bad is a string, not an object!Deep Cloning with JSON
JSON.parse(JSON.stringify(obj)) is a quick way to deep clone objects — but it has limitations:
// Quick deep clone
const original = { a: 1, b: { c: 2, d: [3, 4] } };
const clone = JSON.parse(JSON.stringify(original));
clone.b.c = 99;
console.log(original.b.c); // 2 — original unchanged
// ⚠️ Limitations: loses functions, dates, undefined, Map, Set
// Use structuredClone() instead (modern browsers + Node 17+)
const betterClone = structuredClone(original);The toJSON() Method
If an object has a toJSON() method, JSON.stringify() calls it automatically:
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
}
// Control what gets serialized
toJSON() {
return {
name: this.name,
email: this.email,
// password intentionally excluded
};
}
}
const user = new User("Alice", "alice@example.com", "secret");
JSON.stringify(user);
// '{"name":"Alice","email":"alice@example.com"}'
// password is not includedError Handling Patterns
// Safe parse with fallback
function safeParse(text, fallback = null) {
try {
return JSON.parse(text);
} catch {
return fallback;
}
}
safeParse('{"valid": true}'); // { valid: true }
safeParse('not json'); // null
safeParse('not json', {}); // {}
// Parse with error details
function parseWithError(text) {
try {
return { data: JSON.parse(text), error: null };
} catch (e) {
return { data: null, error: e.message };
}
}
const result = parseWithError('{"trailing": "comma",}');
console.log(result.error);
// "Expected double-quoted property name in JSON at position 22"Comparing API responses? Use our JSON Diff tool to see exactly what changed between two responses — additions, deletions, and modifications highlighted with full path tracking.
JSON in TypeScript
JSON.parse() returns any in TypeScript, which defeats type safety. Here's how to handle it:
// Define your type
interface User {
name: string;
age: number;
email: string;
}
// Type assertion (trusts the data)
const user = JSON.parse(jsonString) as User;
// Runtime validation with a type guard (safer)
function isUser(data: unknown): data is User {
return (
typeof data === "object" && data !== null &&
"name" in data && typeof (data as User).name === "string" &&
"age" in data && typeof (data as User).age === "number" &&
"email" in data && typeof (data as User).email === "string"
);
}
const parsed = JSON.parse(jsonString);
if (isUser(parsed)) {
console.log(parsed.name); // TypeScript knows this is a string
} else {
console.error("Invalid user data");
}
// Best: use Zod for runtime validation
// import { z } from "zod";
// const UserSchema = z.object({ name: z.string(), age: z.number(), email: z.string().email() });
// const user = UserSchema.parse(JSON.parse(jsonString));Need TypeScript interfaces from JSON? Paste a sample API response into our JSON Converter and select "TypeScript" to auto-generate type definitions.
Performance Tips
- Avoid parsing in hot loops. Parse once, pass the object around.
JSON.parse()is fast but not free. - Use
Response.json()over manual parse.fetch().then(r => r.json())is optimized internally and avoids creating an intermediate string. - Stream large JSON. For Node.js, use libraries like
stream-jsonorJSONStreamto process large files without loading them entirely into memory. - Consider
JSON.stringify()cost. Serializing large objects (10MB+) blocks the event loop. Move it to a Worker if needed. - Cache parsed results. If you parse the same config or schema multiple times, cache the parsed object.
Common Mistakes
- Double parsing:
JSON.parse(JSON.parse(str))— this happens when a string is double-encoded on the server side. - Parsing an object:
JSON.parse(obj)whenobjis already an object. Checktypeoffirst. - Missing await:
const data = response.json()withoutawait— you get a Promise, not the data. - Circular references:
JSON.stringify()throws on circular objects. Use a replacer to handle them, or use libraries likeflatted. - Assuming order: JSON object key order is not guaranteed by the spec, even though V8 preserves insertion order in practice.
Debug your JSON
Paste JSON from your JavaScript app into our tools to validate, format, and explore the structure.
🔗 Related Tools & Resources
Explore these related JSON tools and guides