Урок 11

Практика: парсинг и преобразование

Работа с JSON, URL, query-параметрами.

JSON

const user = { name: 'Алиса', age: 25 };

const json = JSON.stringify(user);
// '{"name":"Алиса","age":25}'

const parsed = JSON.parse(json);
// { name: 'Алиса', age: 25 }

Форматирование:

JSON.stringify(user, null, 2);

Отбор полей:

JSON.stringify(user, ['name'], 2);
// { "name": "Алиса" }

Замена:

JSON.stringify(user, (key, value) =>
    key === 'password' ? undefined : value
);

Обработка ошибок парсинга

function safeParse(json) {
    try {
        return { data: JSON.parse(json), error: null };
    } catch (e) {
        return { data: null, error: e };
    }
}

URLSearchParams

const params = new URLSearchParams('page=1&sort=date');

params.get('page');         // '1'
params.set('page', '2');
params.append('tag', 'js');
params.has('sort');         // true
params.delete('sort');

params.toString();          // 'page=2&tag=js'

Из объекта:

const params = new URLSearchParams({ page: 1, sort: 'date' });
// page=1&sort=date

В объект:

Object.fromEntries(params);

URL

const url = new URL('https://example.com/path?query=1#hash');

url.protocol; // 'https:'
url.host;     // 'example.com'
url.pathname; // '/path'
url.search;   // '?query=1'
url.hash;     // '#hash'
url.searchParams.get('query'); // '1'

Изменение:

url.pathname = '/new';
url.searchParams.set('page', '2');
url.toString();

Query-параметры

function parseQuery(search) {
    return Object.fromEntries(new URLSearchParams(search));
}

parseQuery('?page=1&sort=date');
// { page: '1', sort: 'date' }

С приведением типов:

function parseQuery(search, schema = {}) {
    const params = Object.fromEntries(new URLSearchParams(search));
    const result = {};

    for (const [key, value] of Object.entries(params)) {
        const type = schema[key];
        if (type === 'number') result[key] = Number(value);
        else if (type === 'boolean') result[key] = value === 'true';
        else result[key] = value;
    }

    return result;
}

parseQuery('?page=1&active=true', { page: 'number', active: 'boolean' });
// { page: 1, active: true }

Десериализация в класс

class User {
    constructor({ name, age }) {
        this.name = name;
        this.age = age;
    }

    static fromJSON(json) {
        return new User(JSON.parse(json));
    }
}

const user = User.fromJSON('{"name":"Алиса","age":25}');

Нормализация

Преобразование массива в словарь:

function normalize(items) {
    const byId = {};
    const allIds = [];

    for (const item of items) {
        byId[item.id] = item;
        allIds.push(item.id);
    }

    return { byId, allIds };
}

Обратно:

function denormalize({ byId, allIds }) {
    return allIds.map((id) => byId[id]);
}

Денормализация вложенного

function flattenPosts(posts) {
    const usersById = {};
    const flatPosts = [];

    for (const post of posts) {
        usersById[post.user.id] = post.user;
        flatPosts.push({
            ...post,
            user: undefined,
            userId: post.user.id,
        });
    }

    return { posts: flatPosts, usersById };
}

Парсинг CSV

function parseCSV(text) {
    const [header, ...rows] = text.trim().split('\n');
    const keys = header.split(',');

    return rows.map((row) => {
        const values = row.split(',');
        return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
    });
}

Сериализация в query

function buildQuery(params) {
    const search = new URLSearchParams();

    for (const [key, value] of Object.entries(params)) {
        if (value === undefined || value === null || value === '') continue;
        if (Array.isArray(value)) {
            value.forEach((v) => search.append(key, String(v)));
        } else {
            search.set(key, String(value));
        }
    }

    return search.toString();
}

buildQuery({ page: 1, tags: ['js', 'react'] });
// 'page=1&tags=js&tags=react'

Итоги

  • JSON.stringify / parse
  • URLSearchParams для query
  • URL для разбора адресов
  • Нормализация для хранилища
  • Приведение типов по схеме
  • Парсинг CSV вручную