Skip to content

Conversation

@nmaks2012
Copy link

@nmaks2012 nmaks2012 commented Oct 23, 2025

SAX-парсинг JSON в пользовательские типы

Этот PR добавляет возможность эффективного парсинга JSON напрямую в пользовательские структуры данных, минуя промежуточное DOM-представление, с использованием SAX-парсеров.

Парсинг через DOM

// JSON-строка с массивом объектов
const std::string json_data = R"([{"id": 1}, {"id": 2}])";

// 1. Парсинг в DOM
formats::json::Value value = formats::json::FromString(json_data);

// 2. Преобразование в C++ тип
auto result = value.As<std::vector<std::map<std::string, int>>>();

Прямой SAX-парсинг для контейнеров STL

Новый механизм использует formats::json::FromStringAs<T>(), под капотом выбирает SAX-парсер для стандартных контейнеров. Он строит C++ объект "на лету", читая JSON-поток, что исключает создание и последующий обход DOM-дерева.

const std::string json_data = R"([{"id": 1}, {"id": 2}])";

// Парсинг в один шаг, напрямую в целевой тип
auto result = formats::json::FromStringAs<std::vector<std::map<std::string, int>>>(json_data);

Иерархическая валидация

Позволяет применять функции-валидаторы на любом уровне вложенности во время SAX-парсинга.

  • Валидация отдельных элементов контейнера

Для этого используется обертка parser::WithValidators. Парсер будет применять указанные валидаторы к каждому элементу коллекции.

Пример: Проверить, что каждый int в векторе — положительный.

void ValidatePositive(int val) {
    if (val <= 0) throw std::runtime_error("Value must be positive");
}

using ValidatedVector = std::vector<formats::json::parser::WithValidators<int, ValidatePositive>>;

// Успех:
auto result = formats::json::FromStringAs<ValidatedVector>("[1, 2, 3]");

// Провал: выбросит исключение parser::ParseError
formats::json::FromStringAs<ValidatedVector>("[1, -2, 3]");
  • Валидация всего контейнера и несколько валидаторов

Валидаторы для всего контейнера передаются как шаблонные аргументы в FromStringAs. Можно применять несколько валидаторов к одному и тому же значению.

Пример: Проверить, что вектор не пустой, и что каждый его элемент — положительное число.

void ValidateNonEmpty(const std::vector<int>& vec) {
    if (vec.empty()) throw std::runtime_error("Vector must not be empty");
}

using ValidatedVector = std::vector<formats::json::parser::WithValidators<int, ValidatePositive>>;

// Применяем ValidateNonEmpty ко всему вектору, а ValidatePositive к каждому элементу
auto result = formats::json::FromStringAs<ValidatedVector, ValidateNonEmpty>("[1, 2, 3]");

// Провал: контейнер пустой
formats::json::FromStringAs<ValidatedVector, ValidateNonEmpty>("[]");

SAX-парсинг для пользовательских типов

Для включения SAX-парсинга для пользовательской структуры, необходимо предоставить для нее метаданные. Это делается путем добавления статической функции DescribeForJsonParsing().

Это позволяет парсеру напрямую сопоставлять ключи JSON с членами C++ структуры, полностью избегая DOM. Такие структуры можно вкладывать друг в друга и в любые контейнеры STL.

Пример:

struct User {
    int id;
    std::string name;

    // 2. Описываем "карту" полей: ключ JSON -> указатель на член класса
    static constexpr auto DescribeForJsonParsing() {
        return std::make_tuple(
            formats::json::parser::Field("user_id", &User::id),
            formats::json::parser::Field("user_name", &User::name)
        );
    }
};

const std::string json_data = R"([{"user_id": 1, "user_name": "Alice"}])";

// Система автоматически "подхватит" ObjectParser для User
auto result = formats::json::FromStringAs<std::vector<User>>(json_data);
  • Валидация пользовательских типов

Система валидации без изменений работает и для пользовательских типов, позволяя проверять как каждый объект, так и весь контейнер.

Пример:

void ValidateUser(const User& user) {
    if (user.id < 0) throw std::runtime_error("User ID cannot be negative");
}

void ValidateUserList(const std::vector<User>& users) {
    if (users.size() > 10) throw std::runtime_error("Too many users");
}

// Тип-инструкция: валидировать каждый объект User
using ValidatedUserList = std::vector<formats::json::parser::WithValidators<User, ValidateUser>>;

const std::string json_data = R"([{"user_id": 1, "user_name": "Alice"}])";

// Валидируем и каждый элемент, и весь контейнер
auto result = formats::json::FromStringAs<ValidatedUserList, ValidateUserList>(json_data);
  • Цепочка выбора парсера и обработка ошибок
    Система выбора парсера работает по следующему приоритету:
  1. Проверяется наличие DescribeForJsonParsing() у типа. Если есть — используется ObjectParser (SAX).
  2. Если нет, система проверяет наличие свободной функции Parse(const json::Value&, To<T>). Если есть — используется старый, более медленный подход через JsonValueProxyParser (DOM).
  3. Если нет ни того, ни другого, и тип является классом/структурой, компиляция будет остановлена с помощью static_assert, который выведет в консоль, подсвеченную инструкцию с примерами кода, объясняющую, как реализовать один из двух способов парсинга.

Бенчмарки

Таблица:
2025-11-16T07:28:07+00:00
Running ./userver-universal-benchmark
Run on (16 X 3686.4 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x16)
  L1 Instruction 32 KiB (x16)
  L2 Unified 4096 KiB (x16)
  L3 Unified 16384 KiB (x1)
Load Average: 1.99, 2.09, 1.90

StdContainer std::vector<std::map<std::string, std::set<int>>>

Benchmark DOM Time DOM Iterations SAX Time SAX Iterations SAX with Validators Time SAX with Validators Iterations SAX faster DOM
StdContainer/8 11883 ns 234866 3404 ns 831848 3412 ns 841924 71.4%
StdContainer/16 23144 ns 120241 6754 ns 414763 6666 ns 419178 70.8%
StdContainer/32 46326 ns 60204 14492 ns 194132 14527 ns 194497 68.7%
StdContainer/64 93574 ns 29792 29872 ns 92509 29773 ns 92473 68.1%
StdContainer/128 187605 ns 15049 61555 ns 44993 60676 ns 46124 67.2%
StdContainer/256 386145 ns 7309 123904 ns 22545 123376 ns 22959 67.9%
StdContainer/512 771221 ns 3632 250197 ns 11333 250038 ns 11176 67.6%

UserType std::vector<BenchObject>

Benchmark DOM Time DOM Iterations SAX Time SAX Iterations SAX with Validators Time SAX with Validators Iterations SAX faster DOM
UserType/8 2943 ns 953598 1138 ns 2465843 1114 ns 2497860 61.3%
UserType/16 5745 ns 485536 2279 ns 1235225 2242 ns 1243306 60.3%
UserType/32 10999 ns 254658 4359 ns 647695 4312 ns 649495 60.4%
UserType/64 21647 ns 128123 8876 ns 313028 8994 ns 312293 59.0%
UserType/128 43538 ns 64427 18100 ns 153798 18199 ns 155105 58.4%
UserType/256 85961 ns 32410 35644 ns 77690 35377 ns 79361 58.5%
UserType/512 174378 ns 16061 70799 ns 39360 71699 ns 39292 59.4%
Raw benchmarks data
2025-11-16T07:28:07+00:00
Running ./userver-universal-benchmark
Run on (16 X 3686.4 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x16)
  L1 Instruction 32 KiB (x16)
  L2 Unified 4096 KiB (x16)
  L3 Unified 16384 KiB (x1)
Load Average: 1.99, 2.09, 1.90
-----------------------------------------------------------------------------
Benchmark                                   Time             CPU   Iterations
-----------------------------------------------------------------------------
StdContainer_Dom/8                      11883 ns        11882 ns       234866
StdContainer_Dom/16                     23144 ns        23143 ns       120241
StdContainer_Dom/32                     46326 ns        46324 ns        60204
StdContainer_Dom/64                     93574 ns        93569 ns        29792
StdContainer_Dom/128                   187605 ns       187595 ns        15049
StdContainer_Dom/256                   386145 ns       386075 ns         7309
StdContainer_Dom/512                   771221 ns       771183 ns         3632
StdContainer_Sax/8                       3404 ns         3404 ns       831848
StdContainer_Sax/16                      6754 ns         6753 ns       414763
StdContainer_Sax/32                     14492 ns        14491 ns       194132
StdContainer_Sax/64                     29872 ns        29864 ns        92509
StdContainer_Sax/128                    61555 ns        61552 ns        44993
StdContainer_Sax/256                   123904 ns       123896 ns        22545
StdContainer_Sax/512                   250197 ns       250183 ns        11333
StdContainer_SaxWithValidators/8         3412 ns         3412 ns       841924
StdContainer_SaxWithValidators/16        6666 ns         6666 ns       419178
StdContainer_SaxWithValidators/32       14527 ns        14526 ns       194497
StdContainer_SaxWithValidators/64       29773 ns        29772 ns        92473
StdContainer_SaxWithValidators/128      60676 ns        60671 ns        46124
StdContainer_SaxWithValidators/256     123376 ns       123369 ns        22959
StdContainer_SaxWithValidators/512     250038 ns       250025 ns        11176
UserType_Dom/8                           2943 ns         2943 ns       953598
UserType_Dom/16                          5745 ns         5744 ns       485536
UserType_Dom/32                         10999 ns        10998 ns       254658
UserType_Dom/64                         21647 ns        21645 ns       128123
UserType_Dom/128                        43538 ns        43536 ns        64427
UserType_Dom/256                        85961 ns        85957 ns        32410
UserType_Dom/512                       174378 ns       174331 ns        16061
UserType_Sax/8                           1138 ns         1138 ns      2465843
UserType_Sax/16                          2279 ns         2279 ns      1235225
UserType_Sax/32                          4359 ns         4359 ns       647695
UserType_Sax/64                          8876 ns         8876 ns       313028
UserType_Sax/128                        18100 ns        18099 ns       153798
UserType_Sax/256                        35644 ns        35643 ns        77690
UserType_Sax/512                        70799 ns        70794 ns        39360
UserType_SaxWithValidators/8             1114 ns         1114 ns      2497860
UserType_SaxWithValidators/16            2242 ns         2242 ns      1243306
UserType_SaxWithValidators/32            4312 ns         4311 ns       649495
UserType_SaxWithValidators/64            8994 ns         8993 ns       312293
UserType_SaxWithValidators/128          18199 ns        18198 ns       155105
UserType_SaxWithValidators/256          35377 ns        35375 ns        79361
UserType_SaxWithValidators/512          71699 ns        71693 ns        39292
  • Значительное ускорение до 71% для STL контейнеров любой вложенности, и до 61% для пользовательских типов.

Closes #987


Note: by creating a PR or an issue you automatically agree to the CLA. See CONTRIBUTING.md. Feel free to remove this note, the agreement holds.


/// Parse T from JSON string
template <typename T>
T FromStringAs(std::string_view doc) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а как это работает с валидаторами? minItems и т.п.


private:
ItemParser& item_parser_;
ItemParser item_parser_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мы не можем менять существующее поведение у текущих классов
они используются в старом кодгене OpenAPI внутри Яндекса (не хаотик)

@nmaks2012 nmaks2012 force-pushed the feature/json-sax-parsing-for-chaotic branch from 7376223 to b13636f Compare November 16, 2025 08:26
@nmaks2012 nmaks2012 closed this Nov 16, 2025
@nmaks2012 nmaks2012 force-pushed the feature/json-sax-parsing-for-chaotic branch from 9fc99cc to 85be979 Compare November 16, 2025 08:50
@nmaks2012 nmaks2012 reopened this Nov 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON SAX parsing for chaotic

2 participants