Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

JSON.stringify Takes Two More Arguments Than You Think

C
Carlo Fontanos
· 2 min read

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.

C
Written by Carlo Fontanos

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.

Message sent!

Your details are only used to reply to you.

Keep reading