JSON — Serialising, Parsing and the Sharp Edges
BeginnerJSON.stringify and JSON.parse move data between JavaScript and the wire. What they silently drop — undefined, functions, Dates, Map, Set — is the part worth knowing.
Overview
Every API call you make involves JSON in both directions, usually handled for you by fetch. Where it becomes your problem is when the round trip is not lossless: JSON has no date type, so Dates become strings and stay strings; undefined values disappear entirely; Map, Set and BigInt fail or vanish. Knowing the loss list is what stops the class of bug where data looks right until something downstream calls .getTime() on a string.
What Survives the Round Trip
JSON supports six types. Anything else is converted or dropped without warning.
JSON.stringify({
str: 'ok', // survives
num: 42, // survives
bool: true, // survives
nil: null, // survives
arr: [1, 2], // survives
obj: { a: 1 }, // survives
undef: undefined, // KEY REMOVED entirely
fn: () => {}, // KEY REMOVED
date: new Date(), // becomes an ISO string, stays a string
map: new Map([[1,2]]), // becomes {} — contents lost
set: new Set([1]), // becomes {} — contents lost
nan: NaN, // becomes null
inf: Infinity, // becomes null
})
// BigInt throws: "Do not know how to serialize a BigInt"
// In an array, undefined becomes null rather than disappearing:
JSON.stringify([1, undefined, 3]) // '[1,null,3]'Reviver and Replacer
Both functions take a hook that lets you fix types on the way through — the standard answer to the Date problem.
// Reviver — runs on every value while parsing
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
)
// Replacer — runs on every value while serialising
JSON.stringify(obj, (key, value) =>
key === 'password' ? undefined : value // strip secrets
)
// Replacer as an allow-list of keys
JSON.stringify(user, ['id', 'name'])
// Third argument: indentation, for logs and files
JSON.stringify(obj, null, 2)Parsing Safely
JSON.parse throws on malformed input, which will happen the first time a server returns an HTML error page with a 200.
// Always guard — a proxy returning an HTML error page is common
function safeParse(text, fallback = null) {
try { return JSON.parse(text) }
catch { return fallback }
}
// With fetch, check the status before parsing
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json() // throws if the body is not JSON
// Deep-cloning with JSON is a common trick, and lossy:
const bad = JSON.parse(JSON.stringify(obj)) // loses Dates, undefined, Map
const good = structuredClone(obj) // keeps Dates, Map, SetKey Points to Remember
- 1JSON supports string, number, boolean, null, array and object — everything else is converted or dropped
- 2undefined and functions are removed from objects entirely, but become null inside arrays
- 3Dates become ISO strings and do not come back as Dates — use a reviver if you need them typed
- 4JSON.parse throws on malformed input; check response.ok before parsing a fetch body
- 5structuredClone() is a better deep clone than JSON round-tripping, which loses Dates, Map and Set
Interview Questions
Sign in to ask AriaWhat happens to a Date object when you JSON.stringify and JSON.parse it?
Which values does JSON.stringify silently drop?
What is wrong with JSON.parse(JSON.stringify(obj)) as a deep clone?
Ask Aria about JSON — Serialising, Parsing and the Sharp Edges
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.