diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 1d925588f..3acf66c54 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -1173,6 +1173,55 @@ struct iterator_input_adapter_factory::value && !std::is_same::value > +struct is_std_contiguous_byte_iterator_impl : std::false_type {}; + +// (the containers are only named for byte types) +template +struct is_std_contiguous_byte_iterator_impl + : std::integral_constant < bool, !std::is_pointer::value + && (std::is_same::iterator>::value + || std::is_same::const_iterator>::value + || std::is_same::value + || std::is_same::value) > {}; + +template +struct is_std_contiguous_byte_iterator + : is_std_contiguous_byte_iterator_impl::value_type>::type> {}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using sentinel_type = IteratorType; + using char_type = typename std::remove_cv::value_type>::type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + if (first == last) + { + // (an empty range must not be dereferenced) + return adapter_type(nullptr, nullptr); + } + const char_type* const begin = std::addressof(*first); + return adapter_type(begin, begin + (last - first)); + } +}; + // General purpose iterator-based input (iterator+sentinel pair; SentinelType // defaults to IteratorType for the common same-type case, but may differ for // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index eedb9c561..a4da8f409 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10282,6 +10282,55 @@ struct iterator_input_adapter_factory::value && !std::is_same::value > +struct is_std_contiguous_byte_iterator_impl : std::false_type {}; + +// (the containers are only named for byte types) +template +struct is_std_contiguous_byte_iterator_impl + : std::integral_constant < bool, !std::is_pointer::value + && (std::is_same::iterator>::value + || std::is_same::const_iterator>::value + || std::is_same::value + || std::is_same::value) > {}; + +template +struct is_std_contiguous_byte_iterator + : is_std_contiguous_byte_iterator_impl::value_type>::type> {}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using sentinel_type = IteratorType; + using char_type = typename std::remove_cv::value_type>::type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + if (first == last) + { + // (an empty range must not be dereferenced) + return adapter_type(nullptr, nullptr); + } + const char_type* const begin = std::addressof(*first); + return adapter_type(begin, begin + (last - first)); + } +}; + // General purpose iterator-based input (iterator+sentinel pair; SentinelType // defaults to IteratorType for the common same-type case, but may differ for // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index 5b8f086fd..0c2ed7ca7 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -32,6 +32,7 @@ using nlohmann::json; #include #include +#include #include #include #include @@ -1409,3 +1410,63 @@ TEST_CASE("indentation runs of every length around the 8-byte skip") CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]"); } #endif + +#if !defined(JSON_NOEXCEPTION) +namespace +{ +/// the value of parsing [first, last), or the message of the exception +template +std::string outcome_of(Iterator first, Iterator last) +{ + try + { + return json::parse(first, last).dump(); + } + catch (const json::exception& e) + { + return e.what(); + } +} + +/// a std::string, std::vector, and as +/// iterator pairs: the same as the byte path (std::list iterators) +void check_iterator_pairs(const std::string& text) +{ + CAPTURE(text) + const std::list bytes(text.begin(), text.end()); + const std::string expected = outcome_of(bytes.begin(), bytes.end()); + const bool expected_accept = json::accept(bytes.begin(), bytes.end()); + + std::string s = text; + CHECK(outcome_of(s.begin(), s.end()) == expected); + CHECK(outcome_of(s.cbegin(), s.cend()) == expected); + CHECK(json::accept(s.cbegin(), s.cend()) == expected_accept); + + const std::vector vc(text.begin(), text.end()); + CHECK(outcome_of(vc.begin(), vc.end()) == expected); + const std::vector vu(text.begin(), text.end()); + CHECK(outcome_of(vu.begin(), vu.end()) == expected); + CHECK(json::accept(vu.begin(), vu.end()) == expected_accept); + const std::vector vs(text.begin(), text.end()); + CHECK(outcome_of(vs.begin(), vs.end()) == expected); +} +} // namespace + +TEST_CASE("deserialization from iterator pairs of std::string and std::vector") +{ + // these are read as the pointer range they are, also before C++20 + for (const char* text : + { + "", " ", "[1, 2.5, \"a\", true, null, {\"k\": [\"v\"]}]", "[1, 2", "{\"a\" 1}", "\"\xC3\xA4\"", "\"\xC3\"", "[1]x", "1e400" + }) + { + check_iterator_pairs(text); + } + check_iterator_pairs(std::string("[1]" "\0" "x", 5)); + + // an empty range is not dereferenced + std::string empty; + CHECK_FALSE(json::accept(empty.begin(), empty.end())); + CHECK_THROWS_AS(json::parse(empty.begin(), empty.end()), json::parse_error&); +} +#endif