JavaScript · Syntax · Intermediate

JSON and Date

10 tasks

Serializing and deserializing data with JSON, and working with the Date object.

JSON.parse and JSON.stringify: type mapping and limitations

#
**JSON.parse and JSON.stringify — the basics** ```javascript const obj = { name: 'Alice', age: 30, active: true }; const json = JSON.stringify(obj); // '{"name":"Alice","age":30,"active":true}' const back = JSON.parse(json); // { name: 'Alice', age: 30, active: true } // Pretty-print with indent JSON.stringify(obj, null, 2); // { // "name": "Alice", // "age": 30, // "active": true // } ``` **JSON type mapping** ``` JavaScript JSON ───────────── ────────────────────────────────── string string (always double-quoted) number number (no NaN/Infinity allowed) boolean true / false null null object (plain) object {} array array [] undefined (dropped — key/value pair removed) function (dropped) Symbol (dropped) Date string (ISO 8601 via .toISOString()) Map / Set {} (empty — not serialized properly) ``` **JSON limitations** ```javascript JSON.stringify({ a: undefined, b: 1 }) // '{"b":1}' — undefined silently dropped JSON.stringify({ n: NaN }) // '{"n":null}' — NaN becomes null JSON.stringify({ n: Infinity }) // '{"n":null}' — same // Circular references throw: const a = {}; a.self = a; JSON.stringify(a); // TypeError: Converting circular structure to JSON ``` **JSON.parse security note** `JSON.parse` is safe — it cannot execute code. Unlike `eval()`, it only parses data. But it will throw `SyntaxError` on invalid JSON, so always wrap in try/catch when parsing untrusted input: ```javascript function safeParse(str) { try { return { ok: true, value: JSON.parse(str) }; } catch { return { ok: false, value: null }; } } ```

The Date object: creation, UTC methods, and timezone pitfalls

#
**Creating Date objects** ```javascript new Date() // current date and time new Date('2024-03-15') // from ISO string (parsed as UTC midnight) new Date(2024, 2, 15) // year, month (0-indexed!), day — March 15 new Date(2024, 2, 15, 10, 30) // year, month, day, hour, minute Date.now() // current timestamp in ms (no 'new' needed) ``` **The months-are-zero-indexed gotcha** January = 0, December = 11. This trips up almost everyone: ```javascript new Date(2024, 0, 1) // January 1, 2024 — not February new Date(2024, 11, 31) // December 31, 2024 // Safer: always parse from ISO string to avoid the index trap new Date('2024-03-15') // unambiguous ``` **Reading date components** ```javascript const d = new Date('2024-03-15T10:30:00Z'); // UTC methods — safe for date arithmetic d.getUTCFullYear() // 2024 d.getUTCMonth() // 2 (March, 0-indexed) d.getUTCDate() // 15 (day of month) d.getUTCHours() // 10 d.getUTCMinutes() // 30 // Local methods — depend on the user's timezone d.getFullYear() // may differ from UTC if offset crosses midnight d.getMonth() // same caveat ``` **Timezone pitfalls** ```javascript const d = new Date('2024-03-15'); // parsed as UTC midnight d.toISOString(); // '2024-03-15T00:00:00.000Z' — UTC d.getDate(); // may return 14 in UTC-5! (local time is previous day) // Rule: use UTC methods for arithmetic, toISOString() for serialization ``` **Date arithmetic** ```javascript const start = new Date('2024-01-01'); const end = new Date('2024-03-15'); const diffMs = end - start; // 6393600000 ms const diffDays = diffMs / (1000 * 60 * 60 * 24); // 74 days // Add 7 days to a date const next = new Date(start); next.setDate(next.getDate() + 7); ``` **Date.now() for performance timing** ```javascript const t0 = Date.now(); doExpensiveWork(); console.log(`Took ${Date.now() - t0} ms`); ```

replacer, reviver, toJSON(), and structuredClone vs JSON roundtrip

#
**replacer — filter or transform during stringify** The second argument to `JSON.stringify` can be an array of keys (whitelist) or a function called for each key/value: ```javascript const user = { name: 'Alice', password: 'secret', age: 30 }; // Array replacer — keep only these keys JSON.stringify(user, ['name', 'age']) // '{"name":"Alice","age":30}' // Function replacer — transform values JSON.stringify(user, (key, value) => { if (key === 'password') return undefined; // omit sensitive fields if (typeof value === 'number') return value * 2; // transform return value; }); // '{"name":"Alice","age":60}' ``` **reviver — transform during parse** The second argument to `JSON.parse` is called for each parsed value, from the inside out. Use it to convert Date strings back to Date objects: ```javascript const iso = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/; function dateReviver(key, value) { if (typeof value === 'string' && iso.test(value)) { return new Date(value); } return value; } const json = '{"name":"Event","date":"2024-03-15T00:00:00.000Z"}'; const obj = JSON.parse(json, dateReviver); obj.date instanceof Date // true obj.date.getUTCFullYear() // 2024 ``` **toJSON() — customize serialization for a class** If an object has a `toJSON` method, `JSON.stringify` calls it and uses the return value: ```javascript class Temperature { constructor(celsius) { this.celsius = celsius; } toFahrenheit() { return this.celsius * 9/5 + 32; } toJSON() { return { celsius: this.celsius, unit: 'C' }; } } const temp = new Temperature(100); JSON.stringify(temp) // '{"celsius":100,"unit":"C"}' // Without toJSON: '{"celsius":100}' (only own enumerable props) ``` **structuredClone vs JSON roundtrip** ```javascript // JSON roundtrip — simple but loses non-JSON types const clone1 = JSON.parse(JSON.stringify(obj)); // Dates become strings, undefined is dropped, Sets become {} // structuredClone — preserves Date, Map, Set, ArrayBuffer, etc. const clone2 = structuredClone(obj); // Dates stay Date, Maps stay Map — but functions still can't be cloned ``` Use `structuredClone` for general deep-cloning; use JSON roundtrip only when you know the data is plain JSON-compatible.
01

#

Parse a JSON string and return the parsed JavaScript object.

function parseJson(str) {
    // your code here
}

const result = parseJson('{"name": "Alice", "age": 30}');
console.log(result.name);  // 'Alice'
console.log(result.age);   // 30
Solution
function parseJson(str) {
    return JSON.parse(str);
}
02

#

Serialize a JavaScript object to a JSON string with 2-space indentation.

function toJson(obj) {
    // your code here
}

const result = toJson({ name: 'Alice', scores: [88, 92] });
console.log(result);
// {
//   "name": "Alice",
//   "scores": [
//     88,
//     92
//   ]
// }
Solution
function toJson(obj) {
    return JSON.stringify(obj, null, 2);
}
03

#

Create a `Date` object from the ISO string '2024-03-15T14:30:00Z' and return its UTC year, month (1-indexed), and day as an object.

function parseIsoDate(isoStr) {
    // Return { year, month, day } in UTC
}

const result = parseIsoDate('2024-03-15T14:30:00Z');
console.log(result.year);   // 2024
console.log(result.month);  // 3
console.log(result.day);    // 15
Solution
function parseIsoDate(isoStr) {
    const d = new Date(isoStr);
    return {
        year: d.getUTCFullYear(),
        month: d.getUTCMonth() + 1,
        day: d.getUTCDate(),
    };
}
04

#

Return the number of days between two ISO date strings (always positive).

function daysBetween(iso1, iso2) {
    // your code here
}

console.log(daysBetween('2024-01-01', '2024-03-15'));  // 74
console.log(daysBetween('2024-03-15', '2024-01-01'));  // 74
Solution
function daysBetween(iso1, iso2) {
    const MS_PER_DAY = 1000 * 60 * 60 * 24;
    return Math.round(Math.abs(new Date(iso2) - new Date(iso1)) / MS_PER_DAY);
}
05

#

Use a replacer function in `JSON.stringify` to exclude any keys whose values are `null` or `undefined`.

function stringifyClean(obj) {
    // your code here
}

const obj = { name: 'Alice', age: null, score: 88, notes: undefined };
console.log(stringifyClean(obj));
// '{"name":"Alice","score":88}'
Solution
function stringifyClean(obj) {
    return JSON.stringify(obj, (key, value) => {
        if (value === null || value === undefined) return undefined;
        return value;
    });
}
06

#

Use a reviver function in `JSON.parse` to convert any string values that look like ISO dates (YYYY-MM-DD) into `Date` objects.

function parseWithDates(str) {
    // your code here
}

const result = parseWithDates('{"name":"Alice","birthday":"1990-05-21","score":42}');
console.log(result.birthday instanceof Date);  // true
console.log(result.birthday.getUTCFullYear()); // 1990
console.log(result.score);                     // 42 (number, unchanged)
Solution
function parseWithDates(str) {
    return JSON.parse(str, (key, value) => {
        if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
            return new Date(value);
        }
        return value;
    });
}
07

#

Format a Date object as 'YYYY-MM-DD' (ISO date string, UTC).

function formatDate(date) {
    // your code here
}

console.log(formatDate(new Date('2024-03-15T14:30:00Z')));  // '2024-03-15'
console.log(formatDate(new Date('2024-12-01T00:00:00Z')));  // '2024-12-01'
Solution
function formatDate(date) {
    return date.toISOString().slice(0, 10);
}
08

#

Given a JSON array of event objects with 'date' fields (ISO strings), return the event with the most recent date.

function latestEvent(jsonStr) {
    // your code here
}

const events = '[{"name":"Conf","date":"2024-09-12"},{"name":"Workshop","date":"2024-11-03"},{"name":"Meetup","date":"2024-07-20"}]';
console.log(latestEvent(events).name);  // 'Workshop'
Solution
function latestEvent(jsonStr) {
    const events = JSON.parse(jsonStr);
    return events.reduce((latest, e) => e.date > latest.date ? e : latest);
}
09

#

Deep clone a JavaScript object using JSON serialization (without using structuredClone).

function deepClone(obj) {
    // your code here
}

const original = { name: 'Alice', scores: [88, 92], meta: { active: true } };
const clone = deepClone(original);
clone.scores.push(100);
clone.meta.active = false;
console.log(original.scores.length);  // 2 (not affected)
console.log(original.meta.active);    // true (not affected)
Solution
function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
10

#

Given an array of objects with a `toJSON()` method, demonstrate custom JSON serialization: each object should serialize as `{ type, value }` where type is the class name.

class Temperature {
    constructor(celsius) { this.celsius = celsius; }
    toJSON() {
        // your code here — return the object to serialize
    }
}

const temps = [new Temperature(100), new Temperature(0), new Temperature(37)];
const json = JSON.stringify(temps);
console.log(json);
// '[{"type":"Temperature","value":100},{"type":"Temperature","value":0},{"type":"Temperature","value":37}]'
Solution
class Temperature {
    constructor(celsius) { this.celsius = celsius; }
    toJSON() {
        return { type: this.constructor.name, value: this.celsius };
    }
}