JavaScript ships with a built-in JSON object that does exactly two things, and it does them extremely well: JSON.parse turns a JSON string into live values, and JSON.stringify turns values into a JSON string. Between them they power every fetch call, every localStorage save, and every API request body you will ever write. In this lesson of our JSON tutorial series, we go beyond the basics into the options both methods accept.
const text = '{"name":"Ada","skills":["math","logic"]}';
const user = JSON.parse(text);
console.log(user.skills[1]); // "logic"If the string is not valid JSON, JSON.parse throws a SyntaxError. Because network responses and user input are never guaranteed to be well formed, production code wraps parsing in try/catch:
try {
const data = JSON.parse(input);
} catch (err) {
console.error("Bad JSON:", err.message);
}JSON.parse accepts a second argument, a reviver, called for every key and value during parsing. Its classic use is turning ISO date strings back into Date objects:
const data = JSON.parse(text, (key, value) => {
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
});const body = JSON.stringify({ query: "json", page: 2 });
// '{"query":"json","page":2}'Stringify silently handles awkward values: undefined, functions, and symbols are dropped from objects and become null inside arrays. A Date becomes its ISO string because Date defines a toJSON method. Circular references throw a TypeError, which you will meet the first time you stringify a DOM node or a complex framework object.
The third argument controls indentation, which is invaluable for logs and config files:
console.log(JSON.stringify(user, null, 2)); // 2-space indented outputThe second argument, a replacer, controls what gets serialized. Pass an array of keys to whitelist fields, or a function for custom logic such as stripping secrets:
JSON.stringify(user, ["name", "email"]); // only these keys
JSON.stringify(user, (k, v) => k === "password" ? undefined : v);const data = await fetch(url).then(r => r.json()); uses the same parser internally.fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).localStorage.setItem("cart", JSON.stringify(cart)) and parse it back on load.structuredClone(obj) is now preferred, but JSON.parse(JSON.stringify(obj)) remains a widely seen idiom with known limits (drops functions, dates become strings).JSON.parse converts strings to values and throws on invalid input, so wrap it in try/catch.JSON.stringify drops undefined and functions, and throws on circular references.space argument gives you readable, pretty-printed JSON for free.Next up: Working with JSON in Python, where the json module mirrors these ideas with loads and dumps.