diff --git a/universal/include/userver/formats/json/parser/array_parser.hpp b/universal/include/userver/formats/json/parser/array_parser.hpp index 476fe8b81d5b..f7998789a4e5 100644 --- a/universal/include/userver/formats/json/parser/array_parser.hpp +++ b/universal/include/userver/formats/json/parser/array_parser.hpp @@ -12,13 +12,16 @@ namespace formats::json::parser { template > class ArrayParser final : public TypedParser, public Subscriber { public: - explicit ArrayParser(ItemParser& item_parser) : item_parser_(item_parser) { this->item_parser_.Subscribe(*this); } + explicit ArrayParser(ItemParser&& item_parser) : item_parser_(std::make_shared(std::move(item_parser))) { } + explicit ArrayParser(ItemParser& item_parser) : item_parser_(std::make_shared(item_parser)) { } void Reset() override { index_ = 0; state_ = State::kStart; storage_.clear(); + this->item_parser_->Subscribe(*this); + if constexpr (meta::kIsVector) { /* * Heuristics: @@ -88,8 +91,8 @@ class ArrayParser final : public TypedParser, public Subscriber { this->Throw(std::string(what)); } - this->item_parser_.Reset(); - this->parser_state_->PushParser(item_parser_.GetParser()); + this->item_parser_->Reset(); + this->parser_state_->PushParser(item_parser_->GetParser()); index_++; } @@ -103,10 +106,10 @@ class ArrayParser final : public TypedParser, public Subscriber { std::string GetPathItem() const override { return common::GetIndexString(index_ - 1); } - BaseParser& Parser() { return item_parser_.GetParser(); } + BaseParser& Parser() { return item_parser_->GetParser(); } private: - ItemParser& item_parser_; + std::shared_ptr item_parser_; std::optional min_items_, max_items_; enum class State { diff --git a/universal/include/userver/formats/json/parser/map_parser.hpp b/universal/include/userver/formats/json/parser/map_parser.hpp index 21ae32892b7d..8cc11f826341 100644 --- a/universal/include/userver/formats/json/parser/map_parser.hpp +++ b/universal/include/userver/formats/json/parser/map_parser.hpp @@ -11,7 +11,8 @@ class MapParser final : public TypedParser, public Subscriber(std::move(value_parser))) {} + explicit MapParser(ValueParser& value_parser) : value_parser_(std::make_shared(value_parser)) {} void Reset() override { this->state_ = State::kStart; } @@ -30,9 +31,9 @@ class MapParser final : public TypedParser, public SubscriberThrow("object"); key_ = key; - this->value_parser_.Reset(); - this->value_parser_.Subscribe(*this); - this->parser_state_->PushParser(this->value_parser_.GetParser()); + this->value_parser_->Reset(); + this->value_parser_->Subscribe(*this); + this->parser_state_->PushParser(this->value_parser_->GetParser()); } void EndObject() override { @@ -68,7 +69,7 @@ class MapParser final : public TypedParser, public Subscriber value_parser_; }; } // namespace formats::json::parser diff --git a/universal/include/userver/formats/json/parser/meta_parser.hpp b/universal/include/userver/formats/json/parser/meta_parser.hpp new file mode 100644 index 000000000000..16b4629a2eb1 --- /dev/null +++ b/universal/include/userver/formats/json/parser/meta_parser.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace formats::json::parser { + +template +struct Parser; + +template +struct ParserBase { + using type = ParserType; + static std::unique_ptr Create() { return std::make_unique(); } +}; + +template <> +struct Parser : ParserBase {}; + +template <> +struct Parser : ParserBase {}; + +template <> +struct Parser : ParserBase {}; + +template <> +struct Parser : ParserBase {}; + +template <> +struct Parser : ParserBase {}; + +// for custom types +template +struct Parser && !meta::kIsMap>> : ParserBase> {}; + +template +struct Parser && !meta::kIsMap>> { + using ElementType = typename T::value_type; + using ElementParser = typename Parser::type; + using type = ArrayParser; + + static std::unique_ptr Create() { + auto element_parser = Parser::Create(); + return std::make_unique(std::move(*element_parser)); + } +}; + +template +struct Parser>> { + static_assert(std::is_same_v, "JSON object keys must be strings."); + + using MappedType = typename T::mapped_type; + using MappedParser = typename Parser::type; + using type = MapParser; + + static std::unique_ptr Create() { + auto mapped_parser = Parser::Create(); + return std::make_unique(std::move(*mapped_parser)); + } +}; + +template +auto CreateParser() { + return Parser::Create(); +} + +} // namespace formats::json::parser + +USERVER_NAMESPACE_END \ No newline at end of file diff --git a/universal/include/userver/formats/json/parser/parser.hpp b/universal/include/userver/formats/json/parser/parser.hpp index 0cc48f5a8546..d9b9fcffb0d9 100644 --- a/universal/include/userver/formats/json/parser/parser.hpp +++ b/universal/include/userver/formats/json/parser/parser.hpp @@ -7,6 +7,7 @@ #include #include #include +#include USERVER_NAMESPACE_BEGIN diff --git a/universal/include/userver/formats/json/parser/parser_json.hpp b/universal/include/userver/formats/json/parser/parser_json.hpp index 7e59abec53d4..e83b44cf76fa 100644 --- a/universal/include/userver/formats/json/parser/parser_json.hpp +++ b/universal/include/userver/formats/json/parser/parser_json.hpp @@ -27,12 +27,11 @@ class JsonValueParser final : public TypedParser { void EndArray(size_t) override; std::string Expected() const override; + std::string GetPathItem() const override { return {}; } private: void MaybePopSelf(); - std::string GetPathItem() const override { return {}; } - struct Impl; utils::FastPimpl impl_; }; diff --git a/universal/include/userver/formats/json/parser/parser_json_adapter.hpp b/universal/include/userver/formats/json/parser/parser_json_adapter.hpp new file mode 100644 index 000000000000..6f613020f40d --- /dev/null +++ b/universal/include/userver/formats/json/parser/parser_json_adapter.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace formats::json::parser { + +template +class JsonValueAsAdapterParser final : public Subscriber { +public: + using ResultType = T; + + JsonValueAsAdapterParser() : value_parser_(std::make_unique()) { value_parser_->Subscribe(*this); } + + JsonValueAsAdapterParser(JsonValueAsAdapterParser&& other) noexcept + : value_parser_(std::move(other.value_parser_)), subscriber_(other.subscriber_) { + if (value_parser_) { + value_parser_->Subscribe(*this); + } + } + + JsonValueAsAdapterParser& operator=(JsonValueAsAdapterParser&& other) noexcept { + if (this != &other) { + value_parser_ = std::move(other.value_parser_); + subscriber_ = other.subscriber_; + if (value_parser_) { + value_parser_->Subscribe(*this); + } + } + return *this; + } + + JsonValueAsAdapterParser(const JsonValueAsAdapterParser&) = delete; + JsonValueAsAdapterParser& operator=(const JsonValueAsAdapterParser&) = delete; + + void Reset() { + if (value_parser_) { + value_parser_->Reset(); + } + } + + void Subscribe(Subscriber& subscriber) { subscriber_ = &subscriber; } + + void OnSend(Value&& value) override { + if (subscriber_) { + try { + subscriber_->OnSend(value.As()); + } catch (const std::exception& e) { + throw InternalParseError(std::string("Failed to convert json value to type: ") + e.what()); + } + } + } + + operator BaseParser&() { return value_parser_->GetParser(); } + + BaseParser& GetParser() { return value_parser_->GetParser(); } + +private: + std::unique_ptr value_parser_; + Subscriber* subscriber_{nullptr}; +}; + +} // namespace formats::json::parser + +USERVER_NAMESPACE_END \ No newline at end of file diff --git a/universal/include/userver/formats/json/parser/parser_state.hpp b/universal/include/userver/formats/json/parser/parser_state.hpp index b7e872c6d5f4..10a7c7be6d75 100644 --- a/universal/include/userver/formats/json/parser/parser_state.hpp +++ b/universal/include/userver/formats/json/parser/parser_state.hpp @@ -24,6 +24,8 @@ class ParserState final { void ProcessInput(std::string_view sw); + void ProcessInput(std::istream& is); + void PopMe(BaseParser& parser); [[noreturn]] void ThrowError(const std::string& err_msg); diff --git a/universal/include/userver/formats/json/parser/typed_parser.hpp b/universal/include/userver/formats/json/parser/typed_parser.hpp index d6150038d94b..fc965892a4d8 100644 --- a/universal/include/userver/formats/json/parser/typed_parser.hpp +++ b/universal/include/userver/formats/json/parser/typed_parser.hpp @@ -171,6 +171,22 @@ typename Parser::ResultType ParseSingle(Parser& parser, std::string_view input) return result; } +template +typename Parser::ResultType ParseSingle(Parser& parser, std::istream& is) { + using ResultType = typename Parser::ResultType; + ResultType result{}; + + parser.Reset(); + SubscriberSink sink(result); + parser.Subscribe(sink); + + ParserState state; + state.PushParser(parser); + state.ProcessInput(is); + + return result; +} + } // namespace impl template diff --git a/universal/include/userver/formats/json/serialize.hpp b/universal/include/userver/formats/json/serialize.hpp index 1e284883a52d..19cdbb851090 100644 --- a/universal/include/userver/formats/json/serialize.hpp +++ b/universal/include/userver/formats/json/serialize.hpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -30,6 +31,26 @@ formats::json::Value FromString(std::string_view doc); /// Parse JSON from stream formats::json::Value FromStream(std::istream& is); +/// Parse T from JSON string +template +T FromStringAs(std::string_view doc) { + if (doc.empty()) { + throw ParseException("JSON document is empty"); + } + auto parser = parser::CreateParser(); + return parser::impl::ParseSingle(*parser, doc); +}; + +/// Parse T from JSON stream +template +T FromStreamAs(std::istream& is) { + if (!is) { + throw BadStreamException(is); + } + auto parser = parser::CreateParser(); + return parser::impl::ParseSingle(*parser, is); +}; + /// Serialize JSON to stream void Serialize(const formats::json::Value& doc, std::ostream& os); @@ -89,8 +110,8 @@ struct fmt::formatter : fmt::formatter< constexpr static auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } template - auto format(const USERVER_NAMESPACE::formats::json::Value& value, FormatContext& ctx) - USERVER_FMT_CONST->decltype(ctx.out()) { + auto format(const USERVER_NAMESPACE::formats::json::Value& value, FormatContext& ctx) USERVER_FMT_CONST + -> decltype(ctx.out()) { const USERVER_NAMESPACE::formats::json::impl::StringBuffer buffer(value); return fmt::format_to(ctx.out(), "{}", buffer.GetStringView()); } diff --git a/universal/src/formats/json/parser/parser_state.cpp b/universal/src/formats/json/parser/parser_state.cpp index 8b58928c3ac0..33e648261454 100644 --- a/universal/src/formats/json/parser/parser_state.cpp +++ b/universal/src/formats/json/parser/parser_state.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -42,6 +43,60 @@ struct ParserState::Impl { void PushParser(BaseParser& parser, ParserState& parser_state); [[nodiscard]] std::string GetPath() const; + + template + void ProcessInputImpl(Stream& stream, rapidjson::Reader& reader, const SourceData& source_data) { + size_t pos = 0; + try { + while (!reader.IterativeParseComplete()) { + if (stack.empty()) { + throw InternalParseError("Symbols after end of document"); + } + + if (stack.size() > kDepthParseLimit) + throw InternalParseError( + "Exceeded maximum allowed JSON depth of: " + std::to_string(kDepthParseLimit) + ); + + UASSERT(stack.back().parser); + ParserHandler handler(*stack.back().parser); + + pos = stream.Tell(); + static constexpr auto kParseFlags = static_cast( + rapidjson::kParseDefaultFlags | rapidjson::kParseFullPrecisionFlag + ); + reader.IterativeParseNext(stream, handler); + if (reader.HasParseError()) { + throw ParseError{ + reader.GetErrorOffset(), + GetPath(), + rapidjson::GetParseError_En(reader.GetParseErrorCode()), + }; + } + } + } catch (const ParseError&) { + throw; + } catch (const std::exception& e) { + auto cur_pos = stream.Tell(); + std::string msg = e.what(); + + if constexpr (std::is_same_v) { + if (!source_data.empty() && cur_pos != pos) { + msg += fmt::format(", the latest token was {}", ToLimited(source_data.substr(pos, cur_pos - pos))); + } + } + + throw ParseError{ + cur_pos, + GetPath(), + msg, + }; + } + + if (!stack.empty()) { + throw ParseError(stream.Tell(), "", "data is expected after the end of file"); + } + } }; void ParserState::Impl::PushParser(BaseParser& parser, ParserState& parser_state) { @@ -73,58 +128,18 @@ void ParserState::PushParser(BaseParser& parser) { impl_->PushParser(parser, *th void ParserState::ProcessInput(std::string_view sw) { rapidjson::Reader reader; - rapidjson::MemoryStream is(sw.data(), sw.size()); + rapidjson::MemoryStream stream(sw.data(), sw.size()); reader.IterativeParseInit(); - auto& stack = impl_->stack; - - size_t pos = 0; - try { - while (!reader.IterativeParseComplete()) { - if (stack.empty()) { - throw InternalParseError("Symbols after end of document"); - } - - if (stack.size() > kDepthParseLimit) - throw InternalParseError("Exceeded maximum allowed JSON depth of: " + std::to_string(kDepthParseLimit)); - - UASSERT(stack.back().parser); - ParserHandler handler(*stack.back().parser); - - pos = is.Tell(); - static constexpr auto kParseFlags = - static_cast(rapidjson::kParseDefaultFlags | rapidjson::kParseFullPrecisionFlag); - reader.IterativeParseNext(is, handler); - if (reader.HasParseError()) { - throw ParseError{ - reader.GetErrorOffset(), - impl_->GetPath(), - rapidjson::GetParseError_En(reader.GetParseErrorCode()), - }; - } - } - } catch (const ParseError&) { - throw; - } catch (const std::exception& e) { - auto cur_pos = is.Tell(); - auto msg = - (cur_pos == pos) ? "" : fmt::format(", the latest token was {}", ToLimited(sw.substr(pos, cur_pos - pos))); - throw ParseError{ - cur_pos, - impl_->GetPath(), - e.what() + msg, - }; - } + impl_->ProcessInputImpl(stream, reader, sw); +} - if (is.Tell() != sw.size()) { - throw ParseError( - is.Tell(), "", rapidjson::GetParseError_En(rapidjson::ParseErrorCode::kParseErrorDocumentRootNotSingular) - ); - } +void ParserState::ProcessInput(std::istream& is) { + rapidjson::Reader reader; + rapidjson::IStreamWrapper stream(is); + reader.IterativeParseInit(); - if (!stack.empty()) { - throw ParseError(is.Tell(), "", "data is expected after the end of file"); - } + impl_->ProcessInputImpl(stream, reader, std::string_view{}); } BaseParser& ParserState::GetTopParser() const { diff --git a/universal/src/formats/json/serialize_benchmark.cpp b/universal/src/formats/json/serialize_benchmark.cpp index bf6295e37d76..bc81fb24e7bd 100644 --- a/universal/src/formats/json/serialize_benchmark.cpp +++ b/universal/src/formats/json/serialize_benchmark.cpp @@ -1251,6 +1251,210 @@ void JsonArrayToVariantParseBenchmark(benchmark::State& state) { } BENCHMARK(JsonArrayToVariantParseBenchmark)->Range(16, 4096); +struct SimpleStruct { + int id; + std::string name; + bool active; + std::vector numbers; + + bool operator==(const SimpleStruct& other) const { + return id == other.id && name == other.name && active == other.active && numbers == other.numbers; + } +}; + +SimpleStruct Parse(const formats::json::Value& value, formats::parse::To) { + return SimpleStruct{ + value["id"].As(), + value["name"].As(), + value["active"].As(), + value["numbers"].As>() + }; +} + +std::string BuildSimpleStructJson(size_t array_size) { + std::string numbers_array = "["; + for (size_t i = 0; i < array_size; ++i) { + if (i > 0) numbers_array += ','; + numbers_array += std::to_string(i); + } + numbers_array += ']'; + + return fmt::format( + R"({{"id": 42, "name": "test_{}", "active": true, "numbers": {}}})", + array_size, numbers_array + ); +} + +struct ComplexStruct { + std::map properties; + std::vector> matrix; + std::unordered_map flags; + + bool operator==(const ComplexStruct& other) const { + return properties == other.properties && matrix == other.matrix && flags == other.flags; + } +}; + +ComplexStruct Parse(const formats::json::Value& value, formats::parse::To) { + return ComplexStruct{ + value["properties"].As>(), + value["matrix"].As>>(), + value["flags"].As>() + }; +} + +std::string BuildComplexStructJson(size_t size) { + std::string properties = "{"; + std::string matrix = "["; + std::string flags = "{"; + + for (size_t i = 0; i < size; ++i) { + if (i > 0) { + properties += ','; + matrix += ','; + flags += ','; + } + + properties += fmt::format(R"("key_{}": {})", i, i * 10); + flags += fmt::format(R"("flag_{}": {})", i, i % 2 == 0); + + std::string row = "["; + for (size_t j = 0; j < size; ++j) { + if (j > 0) row += ','; + row += fmt::format("{:.2f}", i * j * 1.5); + } + row += "]"; + matrix += row; + } + + properties += "}"; + matrix += "]"; + flags += "}"; + + return fmt::format(R"({{"properties": {}, "matrix": {}, "flags": {}}})", + properties, matrix, flags); +} + +void SaxParseSimpleStructFromStringAs(benchmark::State& state) { + const auto input = BuildSimpleStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + const auto res = formats::json::FromStringAs(input); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseSimpleStructFromStringAs)->RangeMultiplier(4)->Range(1, 1024); + +void SaxParseSimpleStructFromStreamAs(benchmark::State& state) { + const auto input = BuildSimpleStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + std::istringstream stream(input); + const auto res = formats::json::FromStreamAs(stream); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseSimpleStructFromStreamAs)->RangeMultiplier(4)->Range(1, 1024); + +void SaxParseComplexStructFromStringAs(benchmark::State& state) { + const auto input = BuildComplexStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + const auto res = formats::json::FromStringAs(input); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseComplexStructFromStringAs)->RangeMultiplier(4)->Range(1, 256); + +void SaxParseComplexStructFromStreamAs(benchmark::State& state) { + const auto input = BuildComplexStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + std::istringstream stream(input); + const auto res = formats::json::FromStreamAs(stream); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseComplexStructFromStreamAs)->RangeMultiplier(4)->Range(1, 256); + +void DomParseSimpleStruct(benchmark::State& state) { + const auto input = BuildSimpleStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + auto json = formats::json::FromString(input); + const auto res = json.As(); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(DomParseSimpleStruct)->RangeMultiplier(4)->Range(1, 1024); + +void DomParseComplexStruct(benchmark::State& state) { + const auto input = BuildComplexStructJson(state.range(0)); + for ([[maybe_unused]] auto _ : state) { + auto json = formats::json::FromString(input); + const auto res = json.As(); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(DomParseComplexStruct)->RangeMultiplier(4)->Range(1, 256); + +void SaxParseVectorIntFromStringAs(benchmark::State& state) { + std::string input = "["; + for (int i = 0; i < state.range(0); ++i) { + if (i > 0) input += ','; + input += std::to_string(i); + } + input += "]"; + + for ([[maybe_unused]] auto _ : state) { + const auto res = formats::json::FromStringAs>(input); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseVectorIntFromStringAs)->RangeMultiplier(4)->Range(1, 1024); + +void SaxParseMapStringIntFromStringAs(benchmark::State& state) { + std::string input = "{"; + for (int i = 0; i < state.range(0); ++i) { + if (i > 0) input += ','; + input += fmt::format(R"("key_{}": {})", i, i); + } + input += "}"; + + for ([[maybe_unused]] auto _ : state) { + const auto res = formats::json::FromStringAs>(input); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(SaxParseMapStringIntFromStringAs)->RangeMultiplier(4)->Range(1, 1024); + +void DomParseVectorInt(benchmark::State& state) { + std::string input = "["; + for (int i = 0; i < state.range(0); ++i) { + if (i > 0) input += ','; + input += std::to_string(i); + } + input += "]"; + + for ([[maybe_unused]] auto _ : state) { + auto json = formats::json::FromString(input); + const auto res = json.As>(); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(DomParseVectorInt)->RangeMultiplier(4)->Range(1, 1024); + +void DomParseMapStringInt(benchmark::State& state) { + std::string input = "{"; + for (int i = 0; i < state.range(0); ++i) { + if (i > 0) input += ','; + input += fmt::format(R"("key_{}": {})", i, i); + } + input += "}"; + + for ([[maybe_unused]] auto _ : state) { + auto json = formats::json::FromString(input); + const auto res = json.As>(); + benchmark::DoNotOptimize(res); + } +} +BENCHMARK(DomParseMapStringInt)->RangeMultiplier(4)->Range(1, 1024); + } // namespace USERVER_NAMESPACE_END diff --git a/universal/src/formats/json/serialize_test.cpp b/universal/src/formats/json/serialize_test.cpp index 415f57040139..cb2bc715dda3 100644 --- a/universal/src/formats/json/serialize_test.cpp +++ b/universal/src/formats/json/serialize_test.cpp @@ -1,11 +1,15 @@ #include #include +#include +#include +#include #include #include #include +#include #include #include #include @@ -189,14 +193,16 @@ INSTANTIATE_TEST_SUITE_P( NotSortedTestData{R"({"b":{"b":1},"a":{"a":1}})", R"({"a":{"a":1},"b":{"b":1}})"}, NotSortedTestData{ R"({"c":{"b":1,"a":1},"b":{"b":1,"a":1},"a":1})", - R"({"a":1,"b":{"a":1,"b":1},"c":{"a":1,"b":1}})"}, + R"({"a":1,"b":{"a":1,"b":1},"c":{"a":1,"b":1}})" + }, NotSortedTestData{R"({"b":1,"c":{"c":{"c":1}},"a":1})", R"({"a":1,"b":1,"c":{"c":{"c":1}}})"}, NotSortedTestData{R"({"b":{"b":{"b":1}},"a":{"a":1},"c":1})", R"({"a":{"a":1},"b":{"b":{"b":1}},"c":1})"}, NotSortedTestData{R"({"c":1,"b":{"b":{"b":1}},"a":{"a":1}})", R"({"a":{"a":1},"b":{"b":{"b":1}},"c":1})"}, NotSortedTestData{R"({"c":{"c":1},"a":1,"b":{"b":{"b":1}}})", R"({"a":1,"b":{"b":{"b":1}},"c":{"c":1}})"}, NotSortedTestData{ R"({"b":{"b":{"b":1}},"c":{"c":{"c":1}},"a":1})", - R"({"a":1,"b":{"b":{"b":1}},"c":{"c":{"c":1}}})"}, + R"({"a":1,"b":{"b":{"b":1}},"c":{"c":{"c":1}}})" + }, NotSortedTestData{R"({"b":{"b":{"b":1,"a":1}},"a":1,"c":1})", R"({"a":1,"b":{"b":{"a":1,"b":1}},"c":1})"}, NotSortedTestData{R"({"c":1,"b":{"b":{"b":1,"a":1}},"a":1})", R"({"a":1,"b":{"b":{"a":1,"b":1}},"c":1})"} ) @@ -349,4 +355,567 @@ TEST(JsonToPrettyStringCycle, DISABLED_SortsObjectKeys) { EXPECT_EQ(kPrettyJson, formats::json::ToPrettyString(json)); } +template +T FromStreamAs(const std::string& input) { + std::istringstream stream(input); + return formats::json::FromStreamAs(stream); +} + +TEST(JsonAsTParser, BasicTypes) { + // Test int + EXPECT_EQ(formats::json::FromStringAs("42"), 42); + EXPECT_EQ(formats::json::FromStringAs("-123"), -123); + + EXPECT_EQ(FromStreamAs("42"), 42); + EXPECT_EQ(FromStreamAs("-123"), -123); + + // Test double + EXPECT_DOUBLE_EQ(formats::json::FromStringAs("3.14"), 3.14); + EXPECT_DOUBLE_EQ(formats::json::FromStringAs("-2.5"), -2.5); + + EXPECT_DOUBLE_EQ(FromStreamAs("3.14"), 3.14); + EXPECT_DOUBLE_EQ(FromStreamAs("-2.5"), -2.5); + + // Test bool + EXPECT_EQ(formats::json::FromStringAs("true"), true); + EXPECT_EQ(formats::json::FromStringAs("false"), false); + + EXPECT_EQ(FromStreamAs("true"), true); + EXPECT_EQ(FromStreamAs("false"), false); + + // Test string + EXPECT_EQ(formats::json::FromStringAs("\"hello\""), "hello"); + EXPECT_EQ(formats::json::FromStringAs("\"test string\""), "test string"); + + EXPECT_EQ(FromStreamAs("\"hello\""), "hello"); + EXPECT_EQ(FromStreamAs("\"test string\""), "test string"); +} + +TEST(JsonAsTParser, ArrayBool) { + const std::string input{"[true, false, true]"}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, (std::vector{true, false, true})); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, (std::vector{true, false, true})); +} + +TEST(JsonAsTParser, ArrayInt) { + const std::string input = "[1, 2, 3, 4, 5]"; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, (std::vector{1, 2, 3, 4, 5})); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, (std::vector{1, 2, 3, 4, 5})); +} + +TEST(JsonAsTParser, ArrayString) { + const std::string input = R"(["apple", "banana", "cherry"])"; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, (std::vector{"apple", "banana", "cherry"})); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, (std::vector{"apple", "banana", "cherry"})); +} + +TEST(JsonAsTParser, ArrayDouble) { + const std::string input = "[1.1, 2.2, 3.3]"; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, (std::vector{1.1, 2.2, 3.3})); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, (std::vector{1.1, 2.2, 3.3})); +} + +TEST(JsonAsTParser, MapStringInt) { + const std::string input = R"({"one": 1, "two": 2, "three": 3})"; + + std::map expected{{"one", 1}, {"two", 2}, {"three", 3}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapStringString) { + const std::string input = R"({"name": "John", "city": "New York"})"; + + std::map expected{{"name", "John"}, {"city", "New York"}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapStringBool) { + const std::string input = R"({"active": true, "verified": false})"; + + std::map expected{{"active", true}, {"verified", false}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, UnorderedMapStringInt) { + const std::string input = R"({"x": 10, "y": 20, "z": 30})"; + + std::unordered_map expected{{"x", 10}, {"y", 20}, {"z", 30}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, ArrayOfMapsStringInt) { + const std::string input = R"([{"id": 1, "count": 10}, {"id": 2, "count": 20}])"; + + std::vector> expected{{{"id", 1}, {"count", 10}}, {{"id", 2}, {"count", 20}}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, ArrayOfMapsStringString) { + const std::string input = R"([{"name": "Alice", "role": "admin"}, {"name": "Bob", "role": "user"}])"; + + std::vector> expected{ + {{"name", "Alice"}, {"role", "admin"}}, {{"name", "Bob"}, {"role", "user"}} + }; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapOfArraysInt) { + const std::string input = R"({"numbers": [1, 2, 3], "scores": [10, 20, 30]})"; + + std::map> expected{{"numbers", {1, 2, 3}}, {"scores", {10, 20, 30}}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapOfArraysString) { + const std::string input = R"({"fruits": ["apple", "banana"], "colors": ["red", "blue"]})"; + + std::map> expected{ + {"fruits", {"apple", "banana"}}, {"colors", {"red", "blue"}} + }; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, NestedArrayInt) { + const std::string input = "[[1, 2], [3, 4], [5, 6]]"; + + std::vector> expected{{1, 2}, {3, 4}, {5, 6}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, NestedArrayString) { + const std::string input = R"([["a", "b"], ["c", "d"], ["e", "f"]])"; + + std::vector> expected{{"a", "b"}, {"c", "d"}, {"e", "f"}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, SetTypes) { + const std::string input = R"(["apple", "banana", "apple", "cherry"])"; + + std::set set_expected{"apple", "banana", "cherry"}; + + auto string_set_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_set_result, set_expected); + + auto stream_set_result = FromStreamAs>(input); + EXPECT_EQ(stream_set_result, set_expected); + + std::unordered_set uset_expected{"apple", "banana", "cherry"}; + + auto string_uset_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_uset_result, uset_expected); + + auto stream_uset_result = FromStreamAs>(input); + EXPECT_EQ(stream_uset_result, uset_expected); +} + +TEST(JsonAsTParser, EmptyContainers) { + // Empty array + auto string_empty_vec = formats::json::FromStringAs>("[]"); + EXPECT_TRUE(string_empty_vec.empty()); + + auto stream_empty_vec = FromStreamAs>("[]"); + EXPECT_TRUE(stream_empty_vec.empty()); + + // Empty object + auto string_empty_map = formats::json::FromStringAs>("{}"); + EXPECT_TRUE(string_empty_map.empty()); + + auto stream_empty_map = FromStreamAs>("{}"); + EXPECT_TRUE(stream_empty_map.empty()); + + // Empty set + auto string_empty_set = formats::json::FromStringAs>("[]"); + EXPECT_TRUE(string_empty_set.empty()); + + auto stream_empty_set = FromStreamAs>("[]"); + EXPECT_TRUE(stream_empty_set.empty()); +} + +TEST(JsonAsTParser, ErrorCases) { + using JsonParserError = formats::json::parser::ParseError; + + // Invalid type conversion + EXPECT_THROW((formats::json::FromStringAs("\"not_a_number\"")), JsonParserError); + EXPECT_THROW((FromStreamAs("\"not_a_number\"")), JsonParserError); + + // Missing field in object + EXPECT_THROW((formats::json::FromStringAs>("{\"a\": 1, \"b\":}")), JsonParserError); + EXPECT_THROW((FromStreamAs>("{\"a\": 1, \"b\":}")), JsonParserError); + + // Malformed JSON + EXPECT_THROW((formats::json::FromStringAs>("[1, 2, ")), JsonParserError); + EXPECT_THROW((FromStreamAs>("[1, 2, ")), JsonParserError); + + // Type mismatch in array + EXPECT_THROW((formats::json::FromStringAs>("[1, \"two\", 3]")), JsonParserError); + EXPECT_THROW((FromStreamAs>("[1, \"two\", 3]")), JsonParserError); + + // Type mismatch in map + EXPECT_THROW( + (formats::json::FromStringAs>("{\"a\": 1, \"b\": \"text\"}")), JsonParserError + ); + EXPECT_THROW((FromStreamAs>("{\"a\": 1, \"b\": \"text\"}")), JsonParserError); +} + +struct CustomType { + int x; + int y; + + bool operator==(const CustomType& other) const { return x == other.x && y == other.y; } +}; + +CustomType Parse(const formats::json::Value& value, formats::parse::To) { + return CustomType{value["x"].As(), value["y"].As()}; +} + +TEST(JsonAsTParser, CustomTypeSimple) { + const std::string input = R"({"x": 10, "y": 20})"; + + CustomType expected{10, 20}; + + auto string_result = formats::json::FromStringAs(input); + EXPECT_EQ(string_result.x, expected.x); + EXPECT_EQ(string_result.y, expected.y); + + auto stream_result = FromStreamAs(input); + EXPECT_EQ(stream_result.x, expected.x); + EXPECT_EQ(stream_result.y, expected.y); +} + +TEST(JsonAsTParser, CustomTypeInVector) { + const std::string input = R"([{"x": 1, "y": 2}, {"x": 3, "y": 4}])"; + + std::vector expected{{1, 2}, {3, 4}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, CustomTypeInMap) { + const std::string input = R"({"point1": {"x": 1, "y": 2}, "point2": {"x": 3, "y": 4}})"; + + std::map expected{{"point1", {1, 2}}, {"point2", {3, 4}}}; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, VectorOfCustomTypeInMap) { + const std::string input = R"({"line1": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "line2": [{"x": 5, "y": 6}]})"; + + std::map> expected{{"line1", {{1, 2}, {3, 4}}}, {"line2", {{5, 6}}}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapInVectorOfCustomType) { + const std::string input = R"([{"start": {"x": 1, "y": 2}, "end": {"x": 3, "y": 4}}])"; + + std::vector> expected{{{"start", {1, 2}}, {"end", {3, 4}}}}; + + auto string_result = formats::json::FromStringAs>>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>>(input); + EXPECT_EQ(stream_result, expected); +} + +struct ComplexType { + int id; + std::string name; + bool active; + std::vector numbers; + std::map properties; + std::vector points; + std::unordered_map flags; + + bool operator==(const ComplexType& other) const { + return id == other.id && name == other.name && active == other.active && numbers == other.numbers && + properties == other.properties && points == other.points && flags == other.flags; + } +}; + +ComplexType Parse(const formats::json::Value& value, formats::parse::To) { + return ComplexType{ + value["id"].As(), + value["name"].As(), + value["active"].As(), + value["numbers"].As>(), + value["properties"].As>(), + value["points"].As>(), + value["flags"].As>() + }; +} + +TEST(JsonAsTParser, ComplexType) { + const std::string input = R"({ + "id": 42, + "name": "test object", + "active": true, + "numbers": [1, 2, 3, 4, 5], + "properties": { + "color": "red", + "size": "large", + "weight": "heavy" + }, + "points": [ + {"x": 1, "y": 2}, + {"x": 3, "y": 4}, + {"x": 5, "y": 6} + ], + "flags": { + "feature1": true, + "feature2": false, + "feature3": true + } + })"; + + ComplexType expected{ + 42, + "test object", + true, + {1, 2, 3, 4, 5}, + {{"color", "red"}, {"size", "large"}, {"weight", "heavy"}}, + {{1, 2}, {3, 4}, {5, 6}}, + {{"feature1", true}, {"feature2", false}, {"feature3", true}} + }; + + auto string_result = formats::json::FromStringAs(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, VectorOfComplexTypes) { + const std::string input = R"([ + { + "id": 1, + "name": "first", + "active": true, + "numbers": [1, 2], + "properties": {"key1": "value1"}, + "points": [{"x": 1, "y": 1}], + "flags": {"flag1": true} + }, + { + "id": 2, + "name": "second", + "active": false, + "numbers": [3, 4], + "properties": {"key2": "value2"}, + "points": [{"x": 2, "y": 2}], + "flags": {"flag2": false} + } + ])"; + + std::vector expected{ + {1, "first", true, {1, 2}, {{"key1", "value1"}}, {{1, 1}}, {{"flag1", true}}}, + {2, "second", false, {3, 4}, {{"key2", "value2"}}, {{2, 2}}, {{"flag2", false}}} + }; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +TEST(JsonAsTParser, MapOfComplexTypes) { + const std::string input = R"({ + "item1": { + "id": 1, + "name": "first item", + "active": true, + "numbers": [10, 20], + "properties": {"type": "primary"}, + "points": [{"x": 10, "y": 20}], + "flags": {"enabled": true} + }, + "item2": { + "id": 2, + "name": "second item", + "active": false, + "numbers": [30, 40], + "properties": {"type": "secondary"}, + "points": [{"x": 30, "y": 40}], + "flags": {"enabled": false} + } + })"; + + std::map expected{ + {"item1", {1, "first item", true, {10, 20}, {{"type", "primary"}}, {{10, 20}}, {{"enabled", true}}}}, + {"item2", {2, "second item", false, {30, 40}, {{"type", "secondary"}}, {{30, 40}}, {{"enabled", false}}}} + }; + + auto string_result = formats::json::FromStringAs>(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs>(input); + EXPECT_EQ(stream_result, expected); +} + +struct NestedComplexType { + ComplexType main; + std::vector alternatives; + std::map variants; + + bool operator==(const NestedComplexType& other) const { + return main == other.main && alternatives == other.alternatives && variants == other.variants; + } +}; + +NestedComplexType Parse(const formats::json::Value& value, formats::parse::To) { + return NestedComplexType{ + value["main"].As(), + value["alternatives"].As>(), + value["variants"].As>() + }; +} + +TEST(JsonAsTParser, NestedComplexType) { + const std::string input = R"({ + "main": { + "id": 1, + "name": "main object", + "active": true, + "numbers": [1, 2, 3], + "properties": {"main": "true"}, + "points": [{"x": 1, "y": 1}], + "flags": {"main_flag": true} + }, + "alternatives": [ + { + "id": 2, + "name": "alt1", + "active": false, + "numbers": [4, 5], + "properties": {"alt": "1"}, + "points": [{"x": 2, "y": 2}], + "flags": {"alt_flag": false} + }, + { + "id": 3, + "name": "alt2", + "active": true, + "numbers": [6, 7], + "properties": {"alt": "2"}, + "points": [{"x": 3, "y": 3}], + "flags": {"alt_flag": true} + } + ], + "variants": { + "var1": { + "id": 4, + "name": "variant1", + "active": true, + "numbers": [8, 9], + "properties": {"var": "1"}, + "points": [{"x": 4, "y": 4}], + "flags": {"var_flag": true} + }, + "var2": { + "id": 5, + "name": "variant2", + "active": false, + "numbers": [10, 11], + "properties": {"var": "2"}, + "points": [{"x": 5, "y": 5}], + "flags": {"var_flag": false} + } + } + })"; + + NestedComplexType expected{ + {1, "main object", true, {1, 2, 3}, {{"main", "true"}}, {{1, 1}}, {{"main_flag", true}}}, + {{2, "alt1", false, {4, 5}, {{"alt", "1"}}, {{2, 2}}, {{"alt_flag", false}}}, + {3, "alt2", true, {6, 7}, {{"alt", "2"}}, {{3, 3}}, {{"alt_flag", true}}}}, + {{"var1", {4, "variant1", true, {8, 9}, {{"var", "1"}}, {{4, 4}}, {{"var_flag", true}}}}, + {"var2", {5, "variant2", false, {10, 11}, {{"var", "2"}}, {{5, 5}}, {{"var_flag", false}}}}} + }; + + auto string_result = formats::json::FromStringAs(input); + EXPECT_EQ(string_result, expected); + + auto stream_result = FromStreamAs(input); + EXPECT_EQ(stream_result, expected); +} + USERVER_NAMESPACE_END