diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index c4bcde2d9..6557e2e85 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -132,6 +132,7 @@ class input_stream_adapter }; #endif // JSON_NO_IO + // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. template @@ -141,30 +142,46 @@ class iterator_input_adapter using char_type = typename std::iterator_traits::value_type; iterator_input_adapter(IteratorType first, IteratorType last) - : current(std::move(first)), end(std::move(last)) + : current(std::move(first)), end(std::move(last)), current_has_been_consumed(false) {} typename std::char_traits::int_type get_character() { - if (JSON_HEDLEY_LIKELY(current != end)) + + if (JSON_HEDLEY_LIKELY(current_has_been_consumed)) { - auto result = std::char_traits::to_int_type(*current); std::advance(current, 1); - return result; } - return std::char_traits::eof(); + if (JSON_HEDLEY_LIKELY(current != end)) + { + current_has_been_consumed = true; + return std::char_traits::to_int_type(*current); + } + else + { + current_has_been_consumed = false; + return std::char_traits::eof(); + } + } private: - IteratorType current; - IteratorType end; + mutable IteratorType current; + const IteratorType end; + mutable bool current_has_been_consumed; template friend struct wide_string_input_helper; bool empty() const { + if (JSON_HEDLEY_LIKELY(current_has_been_consumed)) + { + std::advance(current, 1); + current_has_been_consumed = false; + } + return current == end; } }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 14bc07d5f..7ab8f71cf 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6208,6 +6208,7 @@ class input_stream_adapter }; #endif // JSON_NO_IO + // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. template @@ -6217,30 +6218,46 @@ class iterator_input_adapter using char_type = typename std::iterator_traits::value_type; iterator_input_adapter(IteratorType first, IteratorType last) - : current(std::move(first)), end(std::move(last)) + : current(std::move(first)), end(std::move(last)), current_has_been_consumed(false) {} typename std::char_traits::int_type get_character() { - if (JSON_HEDLEY_LIKELY(current != end)) + + if (JSON_HEDLEY_LIKELY(current_has_been_consumed)) { - auto result = std::char_traits::to_int_type(*current); std::advance(current, 1); - return result; } - return std::char_traits::eof(); + if (JSON_HEDLEY_LIKELY(current != end)) + { + current_has_been_consumed = true; + return std::char_traits::to_int_type(*current); + } + else + { + current_has_been_consumed = false; + return std::char_traits::eof(); + } + } private: - IteratorType current; - IteratorType end; + mutable IteratorType current; + const IteratorType end; + mutable bool current_has_been_consumed; template friend struct wide_string_input_helper; bool empty() const { + if (JSON_HEDLEY_LIKELY(current_has_been_consumed)) + { + std::advance(current, 1); + current_has_been_consumed = false; + } + return current == end; } }; diff --git a/tests/src/unit-consuming_stream.cpp b/tests/src/unit-consuming_stream.cpp new file mode 100644 index 000000000..7a700c348 --- /dev/null +++ b/tests/src/unit-consuming_stream.cpp @@ -0,0 +1,126 @@ + + + +#include "doctest_compatibility.h" + +#define JSON_TESTS_PRIVATE +#include +using nlohmann::json; + + +//This struct emulate an Input Stream, once a "char" is readed from it it is lost +struct EmulateStream +{ + using difference_type = std::ptrdiff_t; + using value_type = char; + using pointer = const char*; + using reference = const char&; + using iterator_category = std::input_iterator_tag; + + // Like a pop_left() -> consume one char from the underlying buffer + static char ConsumeChar(std::string* str) + { + const char c = str->front(); + str->erase(0, 1); + + return c; + } + + EmulateStream() + : + target{nullptr} + {} + + EmulateStream(std::string* target_) + : + target{target_}, + c{ConsumeChar(target_)} + {} + + EmulateStream& operator++() + { + c = ConsumeChar(target); + + return *this; + } + + bool operator!=(const EmulateStream& rhs) const + { + return rhs.target != target; + } + + reference operator*() const + { + return c; + } + + std::string* target; + char c; + +}; + +EmulateStream CreateBegin(std::string& tgt) +{ + return EmulateStream{&tgt}; +} + +EmulateStream CreateEnd(const std::string& tgt) +{ + return {}; +} + + + +TEST_CASE("consume only needed") +{ + + nlohmann::detail::json_sax_acceptor sax_parser; + + const std::string json_A = R"({ "key_A" : "value_A" })"; + const std::string json_B = R"({ "key_B" : "value_B" })"; + + std::string json_concat_AB = json_A + json_B; + + CHECK( + json::sax_parse( + CreateBegin(json_concat_AB), + CreateEnd(json_concat_AB), + &sax_parser, + nlohmann::detail::input_format_t::json, + false + ) + == true + ); + + + CHECK(json_concat_AB == json_B); + + + CHECK( + json::sax_parse( + CreateBegin(json_concat_AB), + CreateEnd(json_concat_AB), + &sax_parser, + nlohmann::detail::input_format_t::json, + false + ) + == true + ); + + + CHECK(json_concat_AB == ""); + CHECK(json_concat_AB.size() == 0); + + + CHECK( + json::sax_parse( + CreateBegin(json_concat_AB), + CreateEnd(json_concat_AB), + &sax_parser, + nlohmann::detail::input_format_t::json, + false + ) + == false + ); + +} \ No newline at end of file