Parsing JSON in JavaScript is something you'll do hundreds of times as a web developer.
Fortunately, JavaScript has built-in support for it — no libraries needed. But there's more nuance
to JSON.parse()
and JSON.stringify()
than the docs let on. Let's go through it properly.
JSON.parse() — Turning a String into an Object
JSON.parse() takes a JSON-formatted string and converts it into a JavaScript value.
That value is usually an object or array, but it can also be a string, number, boolean, or null:
const jsonString = '{"name": "Alice", "age": 30, "active": true}';
const user = JSON.parse(jsonString);
console.log(user.name); // "Alice"
console.log(user.age); // 30
console.log(user.active); // true
console.log(typeof user); // "object"Notice the input must be a string. A common mistake is trying to parse a value that's already
an object — JSON.parse({}) throws a SyntaxError because it converts the object to
"[object Object]" first, which isn't valid JSON.
Always Wrap JSON.parse() in a try/catch
Here's the thing nobody tells you: JSON.parse() throws a
SyntaxError
if the input is invalid JSON. If you're parsing data from an API, user input, or a file, you should always
handle that error. A single malformed response can crash your app if you don't:
function safeParseJSON(str) {
try {
return { data: JSON.parse(str), error: null };
} catch (err) {
return { data: null, error: err.message };
}
}
const { data, error } = safeParseJSON('{"name": "Alice"}');
if (error) {
console.error('Invalid JSON:', error);
} else {
console.log(data.name); // "Alice"
}
// Handles bad input gracefully
const result = safeParseJSON('not json at all');
console.log(result.error); // "Unexpected token 'o', "not json "... is not valid JSON"I use a wrapper like this in almost every project. It makes error handling consistent and prevents ugly uncaught exceptions from bubbling up to your UI.
JSON.stringify() — Turning an Object into a String
JSON.stringify() does the reverse: it converts a JavaScript value into a JSON string.
You'll use this when sending data to an API, saving to localStorage, or writing to a file:
const user = {
name: "Bob",
age: 25,
roles: ["admin", "editor"],
password: "secret123" // we'll handle this later
};
// Basic usage
const jsonString = JSON.stringify(user);
// '{"name":"Bob","age":25,"roles":["admin","editor"],"password":"secret123"}'
// Pretty-printed (great for logs and file output)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
// "name": "Bob",
// "age": 25,
// "roles": ["admin", "editor"],
// "password": "secret123"
// }The third argument to JSON.stringify() is the indent level. Using 2
or 4 gives you readable output. The default (no third arg) gives you compact, minified JSON
— better for network transmission.
What stringify() Drops Silently
This one bites developers regularly. JSON.stringify() silently omits certain values
because JSON doesn't support them:
const data = {
name: "Alice",
greet: function() { return "hi"; }, // Functions → dropped
undef: undefined, // undefined → dropped
sym: Symbol("key"), // Symbols → dropped
nan: NaN, // NaN → null
inf: Infinity, // Infinity → null
date: new Date("2024-01-15") // Dates → ISO string
};
console.log(JSON.stringify(data, null, 2));
// {
// "name": "Alice",
// "nan": null,
// "inf": null,
// "date": "2024-01-15T00:00:00.000Z"
// }
// greet, undef, sym are GONEundefined values and
then parse it, those keys will be missing entirely. This can cause subtle bugs if your code checks
for obj.key === undefined later.Fetching JSON from an API — The Real-World Pattern
In practice, most JSON parsing happens when you fetch data from an API. The
Fetch API
makes this straightforward — response.json() handles the parsing for you:
async function getUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json(); // parses JSON automatically
return user;
} catch (err) {
console.error('Failed to fetch user:', err);
return null;
}
}
const user = await getUser(42);
if (user) {
console.log(`Hello, ${user.name}!`);
}response.json() is essentially JSON.parse(await response.text()).
It also throws if the response body isn't valid JSON, so it's worth catching that separately
in production code.
Working with Nested JSON Data
Deeply nested JSON is common in real APIs. Here's how you navigate it safely without crashing on missing keys:
const apiResponse = {
"user": {
"profile": {
"address": {
"city": "Berlin"
}
}
}
};
// Unsafe — throws if any level is undefined
const city1 = apiResponse.user.profile.address.city; // "Berlin"
// Safe with optional chaining (ES2020+) — returns undefined instead of throwing
const city2 = apiResponse?.user?.profile?.address?.city; // "Berlin"
const zip = apiResponse?.user?.profile?.address?.zip; // undefined (not a crash)
// With a fallback using nullish coalescing
const country = apiResponse?.user?.profile?.address?.country ?? "Unknown";Optional chaining (?.)
and nullish coalescing (??)
are modern JavaScript features — both part of the
ECMAScript spec since 2020 — that
make working with uncertain JSON structures much safer. Use them freely — they're supported in all
modern browsers and Node.js 14+.
The replacer and reviver Functions
Both JSON.stringify() and JSON.parse() accept a second argument
that lets you customise the transformation. These are genuinely useful for real-world scenarios:
// replacer: filter or transform values during stringify
const user = { name: "Alice", password: "s3cr3t", age: 30 };
const safe = JSON.stringify(user, ["name", "age"]); // only include these keys
// '{"name":"Alice","age":30}' — password is excluded
// reviver: transform values during parse
const dateJson = '{"name": "Alice", "createdAt": "2024-01-15T09:30:00Z"}';
const parsed = JSON.parse(dateJson, (key, value) => {
if (key === "createdAt") return new Date(value); // convert string to Date
return value;
});
console.log(parsed.createdAt instanceof Date); // true
console.log(parsed.createdAt.getFullYear()); // 2024The reviver pattern is especially useful for restoring Date objects after
round-tripping through JSON, since JSON doesn't have a native date type.
Useful Tools
When working with JSON in JavaScript projects, these tools save time: JSON Formatter to pretty-print minified API responses, JSON Validator to check for syntax errors, JSON Path to query specific fields from large payloads, and JSON Escape when you need to embed JSON in a string. For more depth, the MDN JSON reference is excellent.
Wrapping Up
JSON.parse() and JSON.stringify() are the two functions you need.
The key habits to build: always wrap parse() in try/catch, use optional chaining for
nested access, and know what stringify() silently drops. Get those three right and
you'll handle 99% of real-world JSON scenarios without surprises.