JavaScript Interview Prep
Miscellaneous

JSON.parse / JSON.stringify

The Translator With a Limited Dialect

LinkedIn Hook

JSON.stringify({ a: undefined, b: () => {}, c: NaN }) returns "{"c":null}".

Two of your three properties silently vanished. The third turned into null. No error. No warning. Just quietly broken data on its way to your API.

These are the JSON serialization edge cases that cause real production bugs — Maps and Sets become empty objects, Dates become strings that don't auto-revive, BigInts throw, circular references throw, and the replacer/reviver hooks that would save you are things most developers have never used.

In this lesson you'll learn which values JSON can't represent, how arrays and objects disagree on missing values, why structuredClone is the modern cloning answer, and how to use toJSON, replacer, and reviver to keep serialization under your control.

If you've ever logged an object to an API and seen data missing on the other side — this lesson fixes it.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #JSON #Serialization #Frontend #CodingInterview #WebDevelopment


JSON.parse / JSON.stringify thumbnail


What You'll Learn

  • Which JavaScript values JSON silently drops, mutates, or throws on
  • How to use replacer, reviver, and toJSON to control serialization
  • When to prefer structuredClone over a JSON roundtrip

The Translator With a Limited Dialect

Think of JSON.stringify as a translator who only speaks a limited dialect. Hand it a regular object and it does great. But hand it something exotic — a function, a Symbol, Infinity — and it either silently drops it or converts it to something you didn't expect. This is the source of countless production bugs.

What JSON Does NOT Support

Input ValueJSON.stringify OutputSurprise Level
undefinedOmitted (in object) / null (in array)High
function() {}Omitted (in object) / null (in array)High
Symbol("x")Omitted (in object) / null (in array)High
Infinity"null"High
-Infinity"null"High
NaN"null"High
new Date()"2026-04-10T..." (string)Medium
new Map()"{}" (empty object)High
new Set()"{}" (empty object)High
RegExp"{}" (empty object)Medium
BigIntThrows TypeErrorCritical
Circular refThrows TypeErrorCritical
const edgeCases = {
  a: undefined,          // disappears
  b: function() {},      // disappears
  c: Symbol("test"),     // disappears
  d: NaN,                // becomes null
  e: Infinity,           // becomes null
  f: new Date(),         // becomes string
  g: /regex/gi,          // becomes {}
  h: new Map([["k", 1]]), // becomes {}
  i: new Set([1, 2, 3])  // becomes {}
};

console.log(JSON.stringify(edgeCases, null, 2));
// {
//   "d": null,
//   "e": null,
//   "f": "2026-04-10T00:00:00.000Z",
//   "g": {},
//   "h": {},
//   "i": {}
// }
// a, b, c completely GONE!

Arrays Handle Missing Values Differently

const arr = [undefined, function(){}, Symbol("x"), NaN, Infinity];
console.log(JSON.stringify(arr));
// [null, null, null, null, null]
// In arrays, unsupported values become null (not omitted)

Date Becomes String (Cannot Auto-Restore)

const obj = { created: new Date("2026-04-10") };
const json = JSON.stringify(obj);
const parsed = JSON.parse(json);

console.log(typeof parsed.created); // "string" — NOT a Date object!
console.log(parsed.created instanceof Date); // false

Circular Reference Throws

const a = {};
const b = { ref: a };
a.ref = b; // circular!

try {
  JSON.stringify(a);
} catch (e) {
  console.log(e.message); // "Converting circular structure to JSON"
}

BigInt Throws

try {
  JSON.stringify({ value: 42n });
} catch (e) {
  console.log(e.message); // "Do not know how to serialize a BigInt"
}

Replacer Function

The second argument to JSON.stringify is a replacer — a filter/transformer for values:

const data = {
  name: "Rakibul",
  password: "secret123",
  age: 25,
  role: "admin"
};

// Filter out sensitive fields
const safe = JSON.stringify(data, (key, value) => {
  if (key === "password") return undefined; // omit
  return value;
}, 2);

console.log(safe);
// {
//   "name": "Rakibul",
//   "age": 25,
//   "role": "admin"
// }

// Or pass an array of allowed keys
const filtered = JSON.stringify(data, ["name", "role"]);
console.log(filtered); // {"name":"Rakibul","role":"admin"}

Reviver Function

The second argument to JSON.parse is a reviver — a transformer for parsed values:

const json = '{"name":"Rakibul","created":"2026-04-10T00:00:00.000Z","age":25}';

const obj = JSON.parse(json, (key, value) => {
  // Auto-convert ISO date strings back to Date objects
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

console.log(obj.created instanceof Date); // true

toJSON Method

Objects can define a custom toJSON method to control their serialization:

class User {
  constructor(name, password) {
    this.name = name;
    this.password = password;
    this.createdAt = new Date();
  }

  toJSON() {
    return {
      name: this.name,
      createdAt: this.createdAt.toISOString()
      // password intentionally omitted
    };
  }
}

const user = new User("Rakibul", "secret");
console.log(JSON.stringify(user));
// {"name":"Rakibul","createdAt":"2026-04-10T00:00:00.000Z"}

Space Parameter for Pretty Printing

const data = { name: "Rakibul", skills: ["JS", "React"] };

// Compact
JSON.stringify(data);
// {"name":"Rakibul","skills":["JS","React"]}

// 2-space indent
JSON.stringify(data, null, 2);

// Tab indent
JSON.stringify(data, null, "\t");

// Custom string (up to 10 chars)
JSON.stringify(data, null, "-> ");

structuredClone as Alternative

For deep cloning objects that JSON can't handle:

const original = {
  date: new Date(),
  regex: /test/gi,
  map: new Map([["key", "value"]]),
  set: new Set([1, 2, 3]),
  nested: { deep: { value: 42 } }
};

// JSON roundtrip — loses types
const jsonClone = JSON.parse(JSON.stringify(original));
console.log(jsonClone.date instanceof Date); // false
console.log(jsonClone.map);                  // {}

// structuredClone — preserves types
const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true
console.log(clone.map.get("key"));       // "value"
console.log(clone.set.has(2));           // true

// But structuredClone still can't handle:
// - Functions
// - DOM nodes
// - Symbols

JSON.parse / JSON.stringify visual 1


Common Mistakes

  • Assuming a JSON.parse(JSON.stringify(obj)) roundtrip is a safe deep clone. It silently drops functions, undefined, and Symbols, turns NaN / Infinity into null, and converts Dates to strings that no longer instanceof Date.
  • Forgetting that arrays and objects disagree on missing values. In an object, undefined / functions / Symbols are OMITTED entirely. In an array, they become null — which shifts nothing but also looks nothing like the input.
  • Trying to JSON.stringify a BigInt, a circular reference, or a Map/Set full of data and expecting something reasonable. BigInt and cycles throw; Maps and Sets stringify to "{}".

Interview Questions

Q: What happens when you JSON.stringify an object that contains undefined, a function, and a Symbol?

All three are silently omitted from the output. In arrays, they become null. This is because JSON is a data format — it has no concept of functions, Symbols, or undefined. Only string, number, boolean, null, object, and array are valid JSON types.

Q: How do you handle Date objects with JSON.parse/stringify?

Dates become ISO strings during stringify. To restore them, use a reviver function: JSON.parse(json, (key, val) => isDateString(val) ? new Date(val) : val). Alternatively, use structuredClone which preserves Date objects natively.

Q: What is the toJSON method?

If an object has a toJSON() method, JSON.stringify calls it and uses the return value instead of the object itself. This lets you control exactly what gets serialized — useful for omitting sensitive data or transforming values.

Q: Can you JSON.stringify a Map or Set?

No — they both serialize to "{}". To serialize them, convert first: JSON.stringify([...myMap]) for Map entries, or JSON.stringify([...mySet]) for Set values. Then use a reviver to reconstruct.

Q: What does JSON.stringify do with NaN and Infinity?

Both become null. This is because JSON has no concept of NaN or Infinity — they're not valid JSON number values.

Q: What does structuredClone handle that JSON.parse(JSON.stringify()) doesn't?

structuredClone correctly handles: Date, RegExp, Map, Set, ArrayBuffer, Blob, File, ImageData, and circular references. JSON roundtrip loses all of these (Dates become strings, others become {}).


Quick Reference — Cheat Sheet

JSON EDGE CASES — QUICK MAP

undefined in object    -> property OMITTED
undefined in array     -> becomes null
function in object     -> property OMITTED
Symbol                 -> OMITTED (object) / null (array)
NaN / Infinity         -> becomes null
Date                   -> becomes ISO string (not revived!)
Map / Set / RegExp     -> becomes {}
BigInt                 -> throws TypeError
Circular reference     -> throws TypeError

Signatures:
  JSON.stringify(val, replacer, space)
  JSON.parse(str, reviver)
  obj.toJSON()          -> custom serialization hook

Better deep-clone:
  structuredClone(val)  -> preserves Date, Map, Set,
                           RegExp, cycles (no functions/DOM)

Previous: setTimeout vs setInterval -> The Kitchen Timer and the Drifting Metronome Next: Polyfills -> Write Your Own map, bind, and Promise


This is Lesson 14.2 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page