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 roundtrip

#
**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 roundtrip** ```javascript // JSON roundtrip — просто, но теряет не-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 roundtrip — только когда знаешь, что данные совместимы с 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 };
    }
}