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; // Описываем "карту" полей: ключ 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

2 participants