Warming up the neural circuits...
By the end of this chapter you will:
JSON.stringify and JSON.parse — including a fix for lost Datesnew Date() on a server might give a different result than on a user's machineIntl.DateTimeFormat without any libraries/g flag bug that makes regex behave unexpectedly in loopsModern apps communicate through JSON, track everything with Dates, and validate with RegExp. These three APIs are the glue of your application logic.
Junior developers treat them as "magic black boxes" — they work until they throw a cryptic error. Senior developers understand the constraints:
Date objects, Map, or undefined — they get silently dropped or convertednew Date() returns the date in the user's local timezone — not your server's timezone/g flag maintains internal that causes bugs when reused in loops1. — Converting a live object into a string (JSON format) so it can be sent over a network or saved to storage.
2. Deserialization — Converting that JSON string back into a live JavaScript object.
3. The Epoch — January 1, 1970 at 00:00:00 UTC. Every JavaScript Date is stored internally as milliseconds since this exact moment. This is called a "Unix timestamp."
4. Regex Backtracking — How a regex engine searches for a pattern by trying every possible path. Poorly written patterns can create "Catastrophic Backtracking" — exponentially slow performance on long strings.
5. Capture Groups — Parentheses in a regex (...) that capture a portion of the matched text for later use.
When you save state to localStorage, you MUST use JSON.stringify. But here's the trap: Date objects become ISO strings after stringify → parse. When you restore them, they're strings, not Dates. Use a Reviver function with JSON.parse to convert them back.
JSON.stringify(value, replacer, space) — Object to stringConverts a JavaScript value to a JSON string.
const user = {
id: 1,
name: "Alice",
joinedAt: new Date("2024-01-15
| Data Type | Serialized? | What happens | Reviver needed? |
|---|---|---|---|
| String, Number, Boolean | ✅ Yes | Preserved exactly | ❌ No |
| Array, Plain Object | ✅ Yes | Preserved | ❌ No |
Date | 🟡 Partial | Becomes ISO string | ✅ Yes |
undefined (in object) | ❌ Dropped | Key removed silently | N/A |
Function | ❌ Dropped | Key removed silently | N/A |
Map, Set | ❌ No | Becomes {} or [] | ✅ Yes (manual) |
NaN, Infinity | ❌ No | Becomes null | N/A |
| Mistake | What goes wrong | The fix |
|---|---|---|
JSON.parse(null) | Returns null without crashing — but your code may not handle it | Guard: item ? JSON.parse(item) : defaultValue |
new Date("2024-01-01") | Parsed as UTC midnight — shows Jan 31 in UTC-5 timezones | Use "2024-01-01T12:00:00" or slash format: "2024/01/01" |
new Date(2024, 0, 1) month | Month 0 = January, not month 1 | Always comment: // 0 = January, 11 = December |
regex.test() in a loop with /g | lastIndex moves — results are unpredictable | Reset regex.lastIndex = 0 or use str.match() |
safeRead(key) function that reads a value from localStorage, parses it as JSON, and returns a fallback empty object if parsing fails or the key is missing.localStorage.setItem("prefs", '{ bad json }'){} returned instead of crash"2024-05-15" into a localized string: "May 15, 2024" using Intl.DateTimeFormat."2024-05-15""May 15, 2024"isValidPhone(str) function that returns true for phone numbers like "+91 98765 43210", "(555) 123-4567", or "1234567890". Accepts 7–15 digits with optional spaces, dashes, parentheses, or leading +."+91 98765 43210"trueYou store user sessions in localStorage as JSON. Dates become strings after serialization. Write a loadSession() function that parses the session and converts any ISO date strings back to real Date objects using a Reviver.
'{"id":1,"createdAt":"2024-01-15T10:00:00.000Z","expiresAt":"2024-01-16T10:00:00.000Z"}'session.createdAt instanceof Date === trueParse a multi-line log string into an array of objects. Each line has format: [LEVEL] YYYY-MM-DD - message. Extract level, date (as Date object), and message using matchAll and named capture groups.
[ERROR] 2024-05-01 - Database connection failed
[INFO] 2024-05-01 - Server started on port 3000
[WARN] 2024-05-02 - High memory usage detected[{ level: "ERROR", date: Date, message: "Database connection failed" }, ...]What error does JSON.parse() throw on invalid JSON?
The space argument — pretty printing:
JSON.stringify(user, null, 2);
// {
// "id": 1,
// "name": "Alice",
// "joinedAt": "2024-01-15T00:00:00.000Z"
// }Data loss rules — what JSON.stringify drops or converts:
| Data type | In objects | In arrays |
|---|---|---|
undefined | Dropped (key removed) | Becomes null |
Function | Dropped | Becomes null |
Symbol | Dropped | Becomes null |
Date | Converted to ISO string | Converted to ISO string |
Map, Set | Becomes {} or [] | Same |
NaN, Infinity | Becomes null | Becomes null |
BigInt | Throws TypeError | Throws TypeError |
The replacer argument — filter what gets serialized:
const obj = { id: 1, password: "secret", name: "Alice" };
// Only include "id" and "name" — exclude sensitive fields
JSON.stringify(obj, ["id", "name"]);
// '{"id":1,"name":"Alice"}'
// Or use a function
JSON.stringify(obj, (key, value) => {
if (key === "password") return undefined; // Drop it
return value;
});| Returns | Mutates original? | Use when |
|---|---|---|
string | No | Sending data to an , saving to localStorage |
JSON.parse(jsonString, reviver) — String to objectConverts a JSON string back to a JavaScript value.
const json = '{"id":1,"name":"Alice","joinedAt":"2024-01-15T00:00:00.000Z"}';
const user = JSON.parse(json);
user.joinedAt; // "2024-01-15T00:00:00.000Z" — still a string!
user.joinedAt.getMonth; // undefined — it's not a Date objectThe Reviver function — heal Dates during parsing:
const user = JSON.parse(json, (key, value) => {
// Detect ISO date strings and convert to real Date objects
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
});
user.joinedAt; // Date object ✅
user.joinedAt.getMonth(); // 0 (January) ✅Always wrap JSON.parse in try/catch:
function safeParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString);
} catch {
return fallback;
}
}
safeParse('{ bad json }', {}); // Returns {} instead of crashing
safeParse(null, {}); // Returns {} — null throws too!// Current moment (in local timezone)
const now = new Date();
// From a UTC ISO string (safest for cross-timezone apps)
const launch = new Date("2024-01-15T10:30:00Z");
// From components — BEWARE: month is 0-indexed!
// Month 0 = January, Month 11 = December
const jan15 = new Date(2024, 0, 15); // January 15, 2024
const dec25 = new Date(2024, 11, 25); // December 25, 2024
// From a Unix timestamp (milliseconds)
const fromStamp = new Date(1705312200000);The 0-indexed month trap:
// ❌ This is NOT February 1st
new Date(2024, 2, 1); // March 1st! (month 2 = March)
// ✅ Remember: month 0 = January
new Date(2024, 1, 1); // February 1st (month 1 = February)Date.now() — Raw millisecond timestampFaster than new Date() when you don't need a Date object — just a number.
const start = Date.now(); // Milliseconds since Epoch
doHeavyWork();
const elapsed = Date.now() - start;
console.log(`Took ${elapsed}ms`);The key insight: dates are just numbers (milliseconds). Arithmetic is just math.
const ONE_DAY_MS = 24 * 60 * 60 * 1000; // 86400000
const today = new Date();
const tomorrow = new Date(today.getTime() + ONE_DAY_MS);
const lastWeek
const d = new Date("2024-05-15T14:30:00Z");
d.getFullYear(); // 2024
d.getMonth(); // 4 (May — 0-indexed!)
d.getDate(); // 15 (day of the month)
d
Intl.DateTimeFormat — Localized date formattingNever build date strings manually (month + "/" + day + "/" + year). Use the built-in internationalization API.
const date = new Date("2024-05-15");
// Different locales
new Intl.DateTimeFormat("en-US").format(date); // "5/15/2024"
new Intl.DateTimeFormat
Relative time — "3 days ago":
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
rtf.format(-3, "day"); // "3 days ago"
rtf.format(
// Literal syntax (preferred — evaluated at parse time)
const emailRegex = /^\S+@\S+\.\S+$/;
// Constructor syntax (use when pattern is dynamic)
const searchPattern = new RegExp(userInput, "gi");Flags:
g — Global: find all matches, not just the firsti — Case-insensitive: a matches Am — Multiline: ^ and $ match line starts/endss — Dotall: . matches newlines tooregex.test(string) — Boolean validationReturns true if the pattern matches, false if not. Perfect for validation.
const emailRegex = /^\S+@\S+\.\S+$/;
const phoneRegex = /^\+?[\d\s\-()]{7,15}$/;
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
| Returns | Use when |
|---|---|
boolean | Validation — you only need to know if it matches |
string.match(regex) — Extract matchesconst text = "Prices: $12.50, $199.00, $5.99";
// Without /g — returns first match + capture groups
const first = text.match(/\$[\d.]+/);
// ["$12.50"]
// With /g — returns ALL matches (no capture groups)
const all = text
| Returns | Use when |
|---|---|
| `string[] | null` |
(...) — Named extractionParentheses in a pattern capture the matched text for later use.
// Extract year, month, day from a date string
const dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
const [fullMatch, year, month, day] = "2024-05-15".match(
string.matchAll(regex) — All matches WITH capture groupsWhen you need all matches and their capture groups, use matchAll (requires the g flag).
const log = "ERROR user_1 at 10:30; ERROR user_2 at 11:45";
const regex = /ERROR (\w+) at (\d+:\d+)/g;
for (const match of log.matchAll(regex)) {
console
string.replace(regex, replacement) — Find and replace// Simple replacement
"Hello World".replace(/World/, "Alice"); // "Hello Alice"
// Global replacement (all occurrences)
"a-b-c".replace(/-/g, "_"); //
/g flag reuse bugWhen a regex object with /g flag is reused, it tracks internal state (lastIndex). This causes the next call to start searching from where the last one left off.
const re = /\d+/g;
re.test("123"); // true — lastIndex is now 3
re.test("123"); // false — searches from index 3, finds nothing!
re.test("123
Parsing localStorage can crash if the JSON is malformed or missing.
function usePersistentState(key, initialValue) {
const [state, setState] = useState(() => {
try {
const item = window.localStorage.getItem(key);
if (!item) return initialValue;
return JSON.parse(item, (k, v) => {
// Revive ISO date strings back to Date objects
if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v)) {
return new Date(v);
}
return v;
});
} catch {
return initialValue; // Graceful fallback on bad JSON
}
});
const setValue = (value) => {
const next = value instanceof Function ? value(state) : value;
setState(next);
window.localStorage.setItem(key, JSON.stringify(next));
};
return [state, setValue];
}d1 === d2 comparing Dates | Compares object references, not values | Use d1.getTime() === d2.getTime() |
| Case-sensitive regex | /hello/ won't match "Hello" | Add the i flag: /hello/i |