Converting between JavaScript values and JSON text is common enough (API payloads, localStorage, deep-cloning in older code) to be worth knowing the edge cases of.
JSON.stringify({ a: 1, b: undefined, c: function () {} });
// '{"a":1}' — undefined values and functions are silently OMITTED, not converted to null
JSON.stringify([1, undefined, 3]);
// '[1,null,3]' — but inside an ARRAY, undefined becomes null instead of being dropped
JSON.stringify({ date: new Date() });
// '{"date":"2026-01-01T00:00:00.000Z"}' — Date is converted to an ISO string
JSON.parse('{"date":"2026-01-01T00:00:00.000Z"}');
// { date: "2026-01-01T00:00:00.000Z" } — comes back as a STRING, not a Date — JSON has no date type
JSON.stringify accepts an optional second argument (a "replacer" function or array of allowed keys) and third argument (indentation, for pretty-printing):
JSON.stringify({ a: 1, b: 2, secret: "hide me" }, ["a", "b"]); // '{"a":1,"b":2}' — only listed keys included
JSON.stringify({ a: 1, b: 2 }, null, 2); // pretty-printed with 2-space indentation
A round-trip through JSON.stringify/JSON.parse is lossy for anything that isn't a plain object, array, string, number, boolean, or null — Map, Set, Date, undefined, functions, and NaN/Infinity (which both become null) all lose information or type unless handled explicitly with a custom replacer/reviver.