JSON.stringify Takes Two More Arguments Than You Think
Most developers use JSON.stringify(value) and JSON.parse(text) with one argument for an entire career, then write helper functions that do exactly what the ignored arguments already do.
The replacer: filter and transform on serialize
As an array, it's an allowlist of keys - instant payload slimming:
JSON.stringify(user, ['id', 'name', 'email']);
// Everything else - internal flags, tokens, circular refs to parents - gone
As a function, it visits every key/value pair, and returning undefined drops the pair - which makes log redaction a one-liner you can actually enforce:
const REDACT = ['password', 'token', 'secret', 'authorization'];
function safeStringify(data) {
return JSON.stringify(data, (key, value) =>
REDACT.some(k => key.toLowerCase().includes(k)) ? '[redacted]' : value
, 2);
}
logger.info(safeStringify(requestContext)); // secrets can't leak into logs
(The third argument there is the also-forgotten indentation parameter - 2 for readable logs, a string like '\t' if you prefer.)
The reviver: fix Dates on the way back in
JSON has no date type, so parse gives you strings where you stored Dates - the round-trip bug that structuredClone was born to fix in memory, but JSON is what crosses the network. The reviver visits every pair during parsing:
const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
const data = JSON.parse(payload, (key, value) =>
typeof value === 'string' && ISO_DATE.test(value) ? new Date(value) : value
);
data.createdAt instanceof Date; // true - deep in the tree, no manual walking
One reviver in your API client, and date handling stops being a per-endpoint chore. Same trick restores Maps, BigInts (serialize as tagged strings), or class instances if you tag them on the way out.
toJSON: objects that know how to serialize themselves
class Money {
constructor(cents, currency) { this.cents = cents; this.currency = currency; }
toJSON() { return { amount: this.cents / 100, currency: this.currency }; }
}
JSON.stringify({ total: new Money(4999, 'USD') });
// {"total":{"amount":49.99,"currency":"USD"}}
If an object has a toJSON method, stringify calls it and serializes the result. Date's ISO output works exactly this way - it's not special-cased, it just has toJSON. Your domain objects can pick their wire format once, centrally, instead of every call site remembering to transform them.
Order of operations worth knowing: toJSON runs first, then the replacer sees its output. And one warning: a replacer/reviver runs on every node - keep them cheap, no async, no surprises. Within those limits, these three hooks quietly replace most of what serialization helper libraries sell.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.