What JSON is
JSON is a text format, not a JavaScript type. It looks like an object literal and is deliberately much smaller: six value types, double quotes only, no trailing commas, no comments, no functions.
{
"name": "Painted", // keys must be double-quoted strings
"days": 5,
"archived": false,
"note": null,
"tags": ["flush", "1mm"],
"spec": { "tolerance": 1 }
}
// not allowed: single quotes, trailing commas, comments,
// undefined, NaN, Infinity, functions, Dates, Maps, Sets
stringify and parse
JSON.stringify(door); // '{"name":"Painted","days":5}'
JSON.stringify(door, null, 2); // indented, for a file or a log
JSON.parse(text); // back to a value
// the second argument picks or transforms keys
JSON.stringify(door, ["name", "days"]);
JSON.stringify(door, (k, v) => (k === "secret" ? undefined : v));
What does not survive the round trip
const saved = JSON.parse(JSON.stringify({ at: new Date() }));
typeof saved.at; // "string", not an object
saved.at.getFullYear(); // TypeError
| Value | After stringify → parse |
|---|---|
undefined in an object | The key disappears entirely |
undefined in an array | Becomes null |
| A function | Disappears (or becomes null in an array) |
NaN, Infinity | null |
A Date | An ISO string |
A Map or Set | {} — an empty object |
A BigInt | Throws a TypeError |
-0 | 0 |
Reviving
A reviver runs for every key from the inside out. It is the clean place to turn date strings back into Date objects, or to validate as you parse.
const ISO = /^\d{4}-\d{2}-\d{2}T/;
const data = JSON.parse(text, (key, value) =>
typeof value === "string" && ISO.test(value) ? new Date(value) : value
);
toJSON
Date already has a toJSON, which is why a date becomes an ISO string rather than an empty object.
class Money {
constructor(minor) { this.minor = minor; }
toJSON() { return { amount: this.minor / 100, currency: "AZN" }; }
}
JSON.stringify({ total: new Money(1234) });
// '{"total":{"amount":12.34,"currency":"AZN"}}'
Parsing can throw
function safeParse(text, fallback = null) {
try {
return JSON.parse(text);
} catch {
return fallback;
}
}
// the most common source of a crash: a truncated or empty response
safeParse(localStorage.getItem("prefs") ?? "null", {});
It returns null, because String(null) is "null", which is valid JSON. JSON.parse("") does throw. Handle both.
Over the network
res.json() throws if the body is not JSON — which happens when a server returns an HTML error page with a 200. If a response can be either, read res.text() and parse it yourself.
const res = await fetch("/api/doors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(door),
});
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const data = await res.json(); // parses for you, and throws on bad JSON
Keys have no order guarantee
JSON.stringify writes keys in the object's own insertion order, so two objects with the same data but different key order produce different strings. That makes JSON.stringify(a) === JSON.stringify(b) a fragile equality test.
If you need a stable string — for a cache key, or a checksum — sort the keys first.
const stable = (o) =>
JSON.stringify(o, Object.keys(o).sort());