JavaScript · Синтаксис · Проміжний

JSON і Date

10 завдань

Серіалізація та десеріалізація даних за допомогою JSON та робота з об'єктом Date.

JSON.parse та JSON.stringify: відповідність типів та обмеження

#
**JSON.parse та JSON.stringify — основи** ```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 } // Читабельний вивід з відступом JSON.stringify(obj, null, 2); // { // "name": "Alice", // "age": 30, // "active": true // } ``` **Відповідність типів JSON** ``` JavaScript JSON ───────────── ────────────────────────────────── string string (завжди в подвійних лапках) number number (NaN/Infinity не допускаються) boolean true / false null null object (звичайний) object {} array array [] undefined (відкидається — пара ключ/значення видаляється) function (відкидається) Symbol (відкидається) Date string (ISO 8601 через .toISOString()) Map / Set {} (порожній — не серіалізується правильно) ``` **Обмеження JSON** ```javascript JSON.stringify({ a: undefined, b: 1 }) // '{"b":1}' — undefined мовчки відкидається JSON.stringify({ n: NaN }) // '{"n":null}' — NaN стає null JSON.stringify({ n: Infinity }) // '{"n":null}' — те саме // Циклічні посилання генерують помилку: const a = {}; a.self = a; JSON.stringify(a); // TypeError: Converting circular structure to JSON ``` **Примітка щодо безпеки JSON.parse** `JSON.parse` безпечний — він не може виконувати код. На відміну від `eval()`, він лише парсить дані. Але він генерує `SyntaxError` при невалідному JSON, тому завжди загортай у try/catch при парсингу ненадійного введення: ```javascript function safeParse(str) { try { return { ok: true, value: JSON.parse(str) }; } catch { return { ok: false, value: null }; } } ```

Об'єкт Date: створення, UTC-методи та пастки часових поясів

#
**Створення об'єктів Date** ```javascript new Date() // поточна дата і час new Date('2024-03-15') // з ISO-рядка (парситься як UTC опівночі) new Date(2024, 2, 15) // рік, місяць (з нуля!), день — 15 березня new Date(2024, 2, 15, 10, 30) // рік, місяць, день, година, хвилина Date.now() // поточна мітка часу в мс (без 'new') ``` **Пастка нумерації місяців з нуля** Січень = 0, Грудень = 11. Це плутає майже всіх: ```javascript new Date(2024, 0, 1) // 1 січня 2024 — не лютий new Date(2024, 11, 31) // 31 грудня 2024 // Безпечніше: завжди парси з ISO-рядка, щоб уникнути пастки індексації new Date('2024-03-15') // однозначно ``` **Читання компонентів дати** ```javascript const d = new Date('2024-03-15T10:30:00Z'); // UTC-методи — безпечні для арифметики дат d.getUTCFullYear() // 2024 d.getUTCMonth() // 2 (березень, з нуля) d.getUTCDate() // 15 (день місяця) d.getUTCHours() // 10 d.getUTCMinutes() // 30 // Локальні методи — залежать від часового поясу користувача d.getFullYear() // може відрізнятися від UTC, якщо зсув перетинає північ ``` **Пастки часових поясів** ```javascript const d = new Date('2024-03-15'); // парситься як UTC опівночі d.toISOString(); // '2024-03-15T00:00:00.000Z' — UTC d.getDate(); // може повернути 14 у UTC-5! (локальний час — попередній день) // Правило: UTC-методи для арифметики, toISOString() для серіалізації ``` **Арифметика дат** ```javascript const start = new Date('2024-01-01'); const end = new Date('2024-03-15'); const diffMs = end - start; // 6393600000 мс const diffDays = diffMs / (1000 * 60 * 60 * 24); // 74 дні // Додати 7 днів до дати const next = new Date(start); next.setDate(next.getDate() + 7); ``` **Date.now() для вимірювання продуктивності** ```javascript const t0 = Date.now(); doExpensiveWork(); console.log(`Зайняло ${Date.now() - t0} мс`); ```

replacer, reviver, toJSON() та structuredClone vs JSON-кругопоїздка

#
**replacer — фільтрація або перетворення при stringify** Другий аргумент `JSON.stringify` може бути масивом ключів (білий список) або функцією, що викликається для кожної пари ключ/значення: ```javascript const user = { name: 'Alice', password: 'secret', age: 30 }; // Масив-replacer — зберегти лише ці ключі JSON.stringify(user, ['name', 'age']) // '{"name":"Alice","age":30}' // Функція-replacer — перетворювати значення JSON.stringify(user, (key, value) => { if (key === 'password') return undefined; // пропустити чутливі поля if (typeof value === 'number') return value * 2; // перетворити return value; }); // '{"name":"Alice","age":60}' ``` **reviver — перетворення при parse** Другий аргумент `JSON.parse` викликається для кожного розпарсеного значення, зсередини назовні. Використовуй для конвертації рядків Date назад у Date-об'єкти: ```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":"Подія","date":"2024-03-15T00:00:00.000Z"}'; const obj = JSON.parse(json, dateReviver); obj.date instanceof Date // true obj.date.getUTCFullYear() // 2024 ``` **toJSON() — налаштування серіалізації для класу** Якщо об'єкт має метод `toJSON`, `JSON.stringify` викликає його і використовує результат: ```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"}' ``` **structuredClone vs JSON-кругопоїздка** ```javascript // JSON-кругопоїздка — просто, але втрачає не-JSON типи const clone1 = JSON.parse(JSON.stringify(obj)); // Date стає рядком, undefined відкидається, Set стає {} // structuredClone — зберігає Date, Map, Set, ArrayBuffer тощо const clone2 = structuredClone(obj); // Date залишається Date, Map залишається Map — але функції клонувати не можна ``` Використовуй `structuredClone` для загального глибокого клонування; JSON-кругопоїздку — лише коли знаєш, що дані сумісні з JSON.
01

#

Розберіть рядок JSON і поверніть розібраний об'єкт JavaScript.

function parseJson(str) {
    // ваш код тут
}

const result = parseJson('{"name": "Alice", "age": 30}');
console.log(result.name);  // 'Alice'
console.log(result.age);   // 30
Рішення
function parseJson(str) {
    return JSON.parse(str);
}
02

#

Серіалізуйте об'єкт JavaScript у рядок JSON з відступом у 2 пробіли.

function toJson(obj) {
    // ваш код тут
}

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

#

Створіть об'єкт `Date` з рядка ISO '2024-03-15T14:30:00Z' і поверніть його UTC-рік, місяць (починаючи з 1) і день як об'єкт.

function parseIsoDate(isoStr) {
    // Повернути { year, month, day } в UTC
}

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

#

Поверніть кількість днів між двома рядками дат ISO (завжди додатнє).

function daysBetween(iso1, iso2) {
    // ваш код тут
}

console.log(daysBetween('2024-01-01', '2024-03-15'));  // 74
console.log(daysBetween('2024-03-15', '2024-01-01'));  // 74
Рішення
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

#

Використайте функцію замінювача в `JSON.stringify`, щоб виключити будь-які ключі, значення яких є `null` або `undefined`.

function stringifyClean(obj) {
    // ваш код тут
}

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

#

Використайте функцію відновлення в `JSON.parse` для перетворення рядкових значень, що виглядають як дати ISO (YYYY-MM-DD), в об'єкти `Date`.

function parseWithDates(str) {
    // ваш код тут
}

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 (число, без змін)
Рішення
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

#

Відформатуйте об'єкт Date як 'YYYY-MM-DD' (рядок дати ISO, UTC).

function formatDate(date) {
    // ваш код тут
}

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'
Рішення
function formatDate(date) {
    return date.toISOString().slice(0, 10);
}
08

#

Для масиву JSON об'єктів подій з полями 'date' (рядки ISO) поверніть подію з найновішою датою.

function latestEvent(jsonStr) {
    // ваш код тут
}

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'
Рішення
function latestEvent(jsonStr) {
    const events = JSON.parse(jsonStr);
    return events.reduce((latest, e) => e.date > latest.date ? e : latest);
}
09

#

Глибоко клонуйте об'єкт JavaScript за допомогою серіалізації JSON (без використання structuredClone).

function deepClone(obj) {
    // ваш код тут
}

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 (не змінений)
console.log(original.meta.active);    // true (не змінений)
Рішення
function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
10

#

Для масиву об'єктів з методом `toJSON()` продемонструйте власну серіалізацію JSON: кожен об'єкт має серіалізуватися як `{ type, value }`, де type — назва класу.

class Temperature {
    constructor(celsius) { this.celsius = celsius; }
    toJSON() {
        // ваш код тут — поверніть об'єкт для серіалізації
    }
}

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}]'
Рішення
class Temperature {
    constructor(celsius) { this.celsius = celsius; }
    toJSON() {
        return { type: this.constructor.name, value: this.celsius };
    }
}