From c60217e8013a0c1b834799cfcd1e45be397cf448 Mon Sep 17 00:00:00 2001 From: Federico Sfriso Date: Sat, 11 Jul 2026 09:13:04 +0200 Subject: [PATCH 1/3] fix: support constructing json from C++20 range views (#4916) (#5205) --- .../nlohmann/detail/conversions/to_json.hpp | 44 ++++++- include/nlohmann/detail/meta/type_traits.hpp | 75 ++++++++++- single_include/nlohmann/json.hpp | 119 +++++++++++++++++- tests/src/unit-regression2.cpp | 40 ++++++ 4 files changed, 270 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index aef1b1a25..a90e23f97 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -177,8 +177,11 @@ struct external_constructor } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < !std::is_same::value, - int > = 0 > + enable_if_t < !std::is_same::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; @@ -218,6 +221,25 @@ struct external_constructor j.set_parents(); j.assert_invariant(); } + + // std::ranges does not work properly on MinGW due to incomplete C++20 support + // see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + template>::value, int> = 0> + static void construct(BasicJsonType& j, CompatibleArrayType && arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + for (auto&& x : std::forward(arr)) + { + j.m_data.m_value.array->push_back(x); + j.set_parent(j.m_data.m_value.array->back()); + } + j.assert_invariant(); + } +#endif }; template<> @@ -355,13 +377,29 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& - !is_basic_json::value, + !is_basic_json::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template < typename BasicJsonType, typename T, + enable_if_t < is_compatible_range_view>::value + && !is_compatible_string_type>::value + && !is_compatible_object_type>::value + && !is_basic_json>::value, int > = 0 > +inline void to_json(BasicJsonType& j, T && arr) +{ + external_constructor::construct(j, std::forward(arr)); +} +#endif + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index e4551d1ba..8a012f195 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -18,6 +18,9 @@ #endif #include #include +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif #include #include #include @@ -450,6 +453,51 @@ struct is_constructible_string_type value_type_t, laundered_type >>::value; }; +// Forward declarations: iteration_proxy.hpp includes this file, so we cannot +// include it here. +template class iteration_proxy; +template class iteration_proxy_value; + +// Identifies nlohmann's internal iteration-proxy types. These must be excluded +// before evaluating any std::ranges concept to avoid circular constraints. +template struct is_iteration_proxy_type : std::false_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; + +// In C++26, std::optional satisfies std::ranges::view; exclude it so the +// range-view overload does not hijack the optional serializer. +#ifdef JSON_HAS_CPP_17 +template struct is_range_view_optional_type : std::false_type {}; +template struct is_range_view_optional_type> : std::true_type {}; +#else +template struct is_range_view_optional_type : std::false_type {}; +#endif + +// std::ranges does not work properly on MinGW due to incomplete C++20 support +// see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + +// SafeToCheck guards against types that trigger circular constraints when +// std::ranges::view is evaluated on GCC 12 / libstdc++ 12: +// - iteration_proxy / iteration_proxy_value directly +// - views wrapping the above (e.g. owning_view>) +// - views wrapping basic_json (e.g. ref_view) — same circularity +// via json's constructors → is_compatible_array_type → here +// nlohmann's plain range_value_t (iterator_traits-based) is safe to call +// before any std::ranges concept is touched, so we use it for the checks. +template < typename T, bool SafeToCheck = + !is_iteration_proxy_type::value && + !is_iteration_proxy_type>::value && + !is_basic_json>::value && + !is_range_view_optional_type::value > +struct is_compatible_range_view : std::false_type {}; + +template +struct is_compatible_range_view + : std::bool_constant> {}; + +#endif + template struct is_compatible_array_type_impl : std::false_type {}; @@ -461,13 +509,38 @@ struct is_compatible_array_type_impl < is_iterator_traits>>::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> + !std::is_same>::value +// When range-view support is enabled, std::ranges::view types (e.g. std::string_view, +// filter_view) can match BOTH this iterator-based specialization AND the view-based one +// below, causing ambiguity. Exclude views here so the two specializations are mutually +// exclusive: this one handles plain iterable containers, the other handles views. +#if JSON_HAS_RANGES && !defined(__MINGW32__) +&& !is_compatible_range_view::value +#endif + >> { static constexpr bool value = is_constructible>::value; }; +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_compatible_range_view::value + && !std::is_same, char>::value + && !std::is_same, wchar_t>::value >> +{ + // CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t + // is safe and correctly handles C++20 iterators that may lack classic iterator_traits. + static constexpr bool value = + is_constructible>::value; +}; +#endif + + template struct is_compatible_array_type : is_compatible_array_type_impl {}; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5395609f0..ae18874b2 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3663,6 +3663,9 @@ NLOHMANN_JSON_NAMESPACE_END // #include +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -4212,6 +4215,51 @@ struct is_constructible_string_type value_type_t, laundered_type >>::value; }; +// Forward declarations: iteration_proxy.hpp includes this file, so we cannot +// include it here. +template class iteration_proxy; +template class iteration_proxy_value; + +// Identifies nlohmann's internal iteration-proxy types. These must be excluded +// before evaluating any std::ranges concept to avoid circular constraints. +template struct is_iteration_proxy_type : std::false_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; + +// In C++26, std::optional satisfies std::ranges::view; exclude it so the +// range-view overload does not hijack the optional serializer. +#ifdef JSON_HAS_CPP_17 +template struct is_range_view_optional_type : std::false_type {}; +template struct is_range_view_optional_type> : std::true_type {}; +#else +template struct is_range_view_optional_type : std::false_type {}; +#endif + +// std::ranges does not work properly on MinGW due to incomplete C++20 support +// see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + +// SafeToCheck guards against types that trigger circular constraints when +// std::ranges::view is evaluated on GCC 12 / libstdc++ 12: +// - iteration_proxy / iteration_proxy_value directly +// - views wrapping the above (e.g. owning_view>) +// - views wrapping basic_json (e.g. ref_view) — same circularity +// via json's constructors → is_compatible_array_type → here +// nlohmann's plain range_value_t (iterator_traits-based) is safe to call +// before any std::ranges concept is touched, so we use it for the checks. +template < typename T, bool SafeToCheck = + !is_iteration_proxy_type::value && + !is_iteration_proxy_type>::value && + !is_basic_json>::value && + !is_range_view_optional_type::value > +struct is_compatible_range_view : std::false_type {}; + +template +struct is_compatible_range_view + : std::bool_constant> {}; + +#endif + template struct is_compatible_array_type_impl : std::false_type {}; @@ -4223,13 +4271,38 @@ struct is_compatible_array_type_impl < is_iterator_traits>>::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> + !std::is_same>::value +// When range-view support is enabled, std::ranges::view types (e.g. std::string_view, +// filter_view) can match BOTH this iterator-based specialization AND the view-based one +// below, causing ambiguity. Exclude views here so the two specializations are mutually +// exclusive: this one handles plain iterable containers, the other handles views. +#if JSON_HAS_RANGES && !defined(__MINGW32__) +&& !is_compatible_range_view::value +#endif + >> { static constexpr bool value = is_constructible>::value; }; +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_compatible_range_view::value + && !std::is_same, char>::value + && !std::is_same, wchar_t>::value >> +{ + // CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t + // is safe and correctly handles C++20 iterators that may lack classic iterator_traits. + static constexpr bool value = + is_constructible>::value; +}; +#endif + + template struct is_compatible_array_type : is_compatible_array_type_impl {}; @@ -6205,8 +6278,11 @@ struct external_constructor } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < !std::is_same::value, - int > = 0 > + enable_if_t < !std::is_same::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; @@ -6246,6 +6322,25 @@ struct external_constructor j.set_parents(); j.assert_invariant(); } + + // std::ranges does not work properly on MinGW due to incomplete C++20 support + // see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + template>::value, int> = 0> + static void construct(BasicJsonType& j, CompatibleArrayType && arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + for (auto&& x : std::forward(arr)) + { + j.m_data.m_value.array->push_back(x); + j.set_parent(j.m_data.m_value.array->back()); + } + j.assert_invariant(); + } +#endif }; template<> @@ -6383,13 +6478,29 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& - !is_basic_json::value, + !is_basic_json::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template < typename BasicJsonType, typename T, + enable_if_t < is_compatible_range_view>::value + && !is_compatible_string_type>::value + && !is_compatible_object_type>::value + && !is_basic_json>::value, int > = 0 > +inline void to_json(BasicJsonType& j, T && arr) +{ + external_constructor::construct(j, std::forward(arr)); +} +#endif + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 62ef843dd..0859c5681 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -1165,6 +1165,46 @@ TEST_CASE("regression tests 2") } #endif +#if JSON_HAS_RANGES && !defined(__MINGW32__) + SECTION("issue #4916 - constructing array from C++20 ranges view does not work") + { + std::vector nums{1, 2, 37, 42, 21}; + auto filteredNums = nums | std::views::filter([](int i) + { + return i > 10; + }); + json const j(filteredNums); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({37, 42, 21})); + } +#endif + + // owning_view is not available in libstdc++ < 12 +#if JSON_HAS_RANGES && !defined(__MINGW32__) && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12) + SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)") + { + json const j(std::vector {1, 2, 37, 42, 21} | std::views::filter([](int i) + { + return i > 10; + })); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({37, 42, 21})); + } +#endif + +#if JSON_HAS_RANGES && !defined(__MINGW32__) + SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)") + { + std::vector nums{1, 2, 3}; + auto t = nums | std::views::transform([](int i) noexcept + { + return i * 2; + }); + json const j(t); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({2, 4, 6})); + } +#endif } TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization) From fe2bcc080f089c3adfd82d02e885eb42fead2251 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 11 Jul 2026 15:09:05 +0200 Subject: [PATCH 2/3] Custom BinaryType direct assignment (#5266) * Fix discussion #4209: custom BinaryType direct assignment and extraction When a custom BinaryType is configured (other than the default std::vector), users can now: 1. Assign values of that type directly to create binary values (not arrays) 2. Extract binary values back to that type with get<>() 3. Extract arrays to that type (for backward compatibility) Implementation: - Add is_compatible_binary_type trait to centralize SFINAE condition - Update to_json to accept custom BinaryType values directly - Update from_json to handle both binary and array inputs for custom BinaryType - Add #include with IWYU comment to from_json.hpp - Add comprehensive tests for assignment and array extraction - Update binary_t documentation with example This is purely additive and invisible to the default nlohmann::json alias, which continues to treat std::vector as arrays. Signed-off-by: Niels Lohmann * Fix CI: missing include and const-correctness - Add #include to type_traits.hpp for the new is_compatible_binary_type trait's std::vector reference (caught by cpplint's include-what-you-use check) - Mark test-local json variables const where never reassigned (caught by clang-tidy's misc-const-correctness check) Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/binary_t.md | 32 +++++++++++++++ .../nlohmann/detail/conversions/from_json.hpp | 21 ++++++++++ .../nlohmann/detail/conversions/to_json.hpp | 9 +++++ include/nlohmann/detail/meta/type_traits.hpp | 9 +++++ single_include/nlohmann/json.hpp | 39 +++++++++++++++++++ tests/src/unit-regression2.cpp | 34 ++++++++++++++++ 6 files changed, 144 insertions(+) diff --git a/docs/mkdocs/docs/api/basic_json/binary_t.md b/docs/mkdocs/docs/api/basic_json/binary_t.md index 2c61ddb28..e273d138f 100644 --- a/docs/mkdocs/docs/api/basic_json/binary_t.md +++ b/docs/mkdocs/docs/api/basic_json/binary_t.md @@ -51,6 +51,38 @@ represent a byte array in modern C++. The default values for `BinaryType` is `#!cpp std::vector`. +#### Custom BinaryType behavior + +When a custom `BinaryType` is configured (other than the default `#!cpp std::vector`), you can assign +values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values +rather than arrays: + +```cpp +using custom_json = nlohmann::basic_json< + nlohmann::ordered_map, // ObjectType + std::vector, // ArrayType + std::string, // StringType + bool, // BooleanType + std::int64_t, // NumberIntegerType + std::uint64_t, // NumberUnsignedType + double, // NumberFloatType + std::allocator, // AllocatorType + nlohmann::adl_serializer, + std::vector // Custom BinaryType +>; + +std::vector data{std::byte{1}, std::byte{2}, std::byte{3}}; +custom_json j = data; // Creates a binary value, not an array +assert(j.is_binary()); + +// Round-tripping works seamlessly +auto extracted = j.get>(); +assert(extracted == data); +``` + +This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations. +The default `nlohmann::json` continues to treat `#!cpp std::vector` as arrays for backward compatibility. + #### Storage Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 65065035f..6856a0965 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -19,6 +19,7 @@ #include // unordered_map #include // pair, declval #include // valarray +#include // vector #include #include @@ -332,6 +333,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType, !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -377,6 +379,25 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin = *j.template get_ptr(); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) +{ + if (j.is_binary()) + { + bin = static_cast(*j.template get_ptr()); + } + else if (j.is_array()) + { + from_json_array_impl(j, bin, priority_tag<3> {}); + } + else + { + JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + } +} + template::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index a90e23f97..5f8644700 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -377,6 +377,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value #if JSON_HAS_RANGES && !defined(__MINGW32__) && !is_compatible_range_view::value @@ -406,6 +407,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi external_constructor::construct(j, bin); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin) +{ + external_constructor::construct(j, typename BasicJsonType::binary_t(bin)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const std::valarray& arr) diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 8a012f195..7036a4ef0 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -13,6 +13,7 @@ #include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval +#include // vector #if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L #include // byte #endif @@ -632,6 +633,14 @@ template struct is_compatible_type : is_compatible_type_impl {}; +template +struct is_compatible_binary_type +{ + static constexpr bool value = + std::is_same::value && + !std::is_same>::value; +}; + template struct is_compatible_reference_type_impl { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index ae18874b2..6952210c1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -189,6 +189,7 @@ #include // unordered_map #include // pair, declval #include // valarray +#include // vector // #include // __ _____ _____ _____ @@ -3592,6 +3593,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval +#include // vector #if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L #include // byte #endif @@ -4394,6 +4396,14 @@ template struct is_compatible_type : is_compatible_type_impl {}; +template +struct is_compatible_binary_type +{ + static constexpr bool value = + std::is_same::value && + !std::is_same>::value; +}; + template struct is_compatible_reference_type_impl { @@ -5537,6 +5547,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType, !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -5582,6 +5593,25 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin = *j.template get_ptr(); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) +{ + if (j.is_binary()) + { + bin = static_cast(*j.template get_ptr()); + } + else if (j.is_array()) + { + from_json_array_impl(j, bin, priority_tag<3> {}); + } + else + { + JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + } +} + template::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) @@ -6478,6 +6508,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value #if JSON_HAS_RANGES && !defined(__MINGW32__) && !is_compatible_range_view::value @@ -6507,6 +6538,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi external_constructor::construct(j, bin); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin) +{ + external_constructor::construct(j, typename BasicJsonType::binary_t(bin)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const std::valarray& arr) diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 0859c5681..7ae4170d3 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -1136,6 +1136,40 @@ TEST_CASE("regression tests 2") CHECK((decoded == json_4804::array())); } + SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping") + { + // Test that assigning a custom BinaryType directly creates a binary value, not an array + const std::vector original{std::byte{1}, std::byte{2}, std::byte{3}}; + const json_4804 j = original; + CHECK(j.is_binary()); + CHECK(!j.is_array()); + + // Test round-tripping: extracting the binary value back as the custom container type + const auto extracted = j.get>(); + CHECK(extracted == original); + + // Test that the default json alias behavior is unchanged: std::vector -> array + const json default_json = std::vector {1, 2, 3}; + CHECK(default_json.is_array()); + CHECK(!default_json.is_binary()); + } + + SECTION("discussion #4209 - custom BinaryType extraction from parsed array") + { + // Test that extracting a custom BinaryType from a parsed JSON array still works + // (not just from a binary-typed node) + const auto j = json_4804::parse("[1,2,3]"); + CHECK(j.is_array()); + CHECK(!j.is_binary()); + + // Extracting as custom BinaryType should work from arrays + const auto extracted = j.get>(); + CHECK(extracted.size() == 3); + CHECK(extracted[0] == std::byte{1}); + CHECK(extracted[1] == std::byte{2}); + CHECK(extracted[2] == std::byte{3}); + } + SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit") { const json jval{}; From ca76c3765049cd1e379186dd3f6432d12eae68f2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 11 Jul 2026 15:09:28 +0200 Subject: [PATCH 3/3] Add iterator+sentinel tests and docs for binary deserializers (#5265) * Add iterator+sentinel tests and docs for binary deserializers This commit extends the C++20 ranges support (iterator+sentinel pairs) to the binary format deserializers from_cbor, from_msgpack, from_ubjson, from_bjdata, and from_bson, matching what was already done for parse(), accept(), and sax_parse(). Changes: - Add istreambuf_sentinel helper to test_utils.hpp for EOF detection in tests - Add 5 new test cases that read binary files directly via std::istreambuf_iterator + sentinel, without pre-buffering - Update documentation for all 5 from_* functions to document overload (3) with SentinelType parameter - All tests pass; verified against existing test suite data - Fix potential buffer over-read warning in heterogeneous iterator test Signed-off-by: Niels Lohmann * Merge iterator+sentinel overloads and fix ambiguity/CI issues Address PR review feedback and CI failures: - Merge the separate same-type and sentinel-type iterator overloads of parse(), accept(), sax_parse(), and the five from_* binary deserializers into a single overload with SentinelType defaulted to IteratorType, as suggested in review. Applied the same simplification to the detail::input_adapter() free functions. - Fix a latent ambiguity: some compilers (e.g. GCC 4.8) unreliably SFINAE the operator!= detection for std::nullptr_t against container/string types, making calls like parse(s, nullptr, ...) ambiguous with the compatible-input overload. can_compare_ne now explicitly excludes std::nullptr_t as a SentinelType. - Use a named enable_if_t template parameter instead of an unnamed function parameter for the SFINAE guard, fixing a clang-tidy hicpp-named-parameter/readability-named-parameter failure. - Update parse.md, accept.md, sax_parse.md, and the five from_*.md pages to document the merged overload instead of separate (2)/(3) overloads, also fixing an over-160-char line that broke the documentation style_check CI job. - Rework the BSON iterator+sentinel test to parse a BSON file already present in the test suite instead of writing/deleting a temp file. Signed-off-by: Niels Lohmann * Fix -Wunneeded-internal-declaration for CustomSentinel in test CustomSentinel lives in an anonymous namespace (internal linkage), and the library's parse loop only ever evaluates the iterator-first direction (it != last), so the reversed-order friend operator!= was never referenced. Clang's -Weverything flags such unused internal declarations as an error. Drop the unused overload; the used direction is enough to satisfy can_compare_ne's either-order detection. Signed-off-by: Niels Lohmann * Fix clang-tidy hicpp-named-parameter and misc-const-correctness - Drop the unused reversed-order operator!= overload from utils::istreambuf_sentinel (only iterator != sentinel is ever evaluated) and name the remaining friend's sentinel parameter, fixing hicpp-named-parameter/readability-named-parameter. - Mark the istreambuf_iterator first/last helper variable const in the five binary-format sentinel tests, fixing misc-const-correctness. Signed-off-by: Niels Lohmann * Fix clang-tidy misc-const-correctness in heterogeneous sentinel test json_str is only read via .data()/.size() and never reassigned, so clang-tidy correctly flags it as const-able. Verified against the exact CI job (silkeh/clang:dev, ci_clang_tidy target) by running clang-tidy directly on this file plus the five binary-format sentinel tests touched by prior commits; all are now clean. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/accept.md | 18 ++- .../mkdocs/docs/api/basic_json/from_bjdata.md | 13 +- docs/mkdocs/docs/api/basic_json/from_bson.md | 13 +- docs/mkdocs/docs/api/basic_json/from_cbor.md | 13 +- .../docs/api/basic_json/from_msgpack.md | 13 +- .../mkdocs/docs/api/basic_json/from_ubjson.md | 13 +- docs/mkdocs/docs/api/basic_json/parse.md | 18 ++- docs/mkdocs/docs/api/basic_json/sax_parse.md | 15 +- .../nlohmann/detail/input/input_adapters.hpp | 80 ++++++++--- include/nlohmann/json.hpp | 56 ++++---- single_include/nlohmann/json.hpp | 136 ++++++++++++------ tests/src/test_utils.hpp | 13 ++ tests/src/unit-bjdata.cpp | 9 ++ tests/src/unit-bson.cpp | 13 ++ tests/src/unit-cbor.cpp | 9 ++ tests/src/unit-msgpack.cpp | 9 ++ tests/src/unit-ubjson.cpp | 9 ++ tests/src/unit-user_defined_input.cpp | 33 +++++ 18 files changed, 366 insertions(+), 117 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/accept.md b/docs/mkdocs/docs/api/basic_json/accept.md index df239713f..dac5f9f36 100644 --- a/docs/mkdocs/docs/api/basic_json/accept.md +++ b/docs/mkdocs/docs/api/basic_json/accept.md @@ -8,8 +8,8 @@ static bool accept(InputType&& i, const bool ignore_trailing_commas = false); // (2) -template -static bool accept(IteratorType first, IteratorType last, +template +static bool accept(IteratorType first, SentinelType last, const bool ignore_comments = false, const bool ignore_trailing_commas = false); ``` @@ -17,10 +17,11 @@ static bool accept(IteratorType first, IteratorType last, Checks whether the input is valid JSON. 1. Reads from a compatible input. -2. Reads from a pair of character iterators +2. Reads from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted - respectively as UTF-8, UTF-16, and UTF-32. + respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to + the iterator type with `operator!=`. Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input (i.e., a parse error) nor creates diagnostic information. @@ -44,6 +45,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep - a pair of `std::string::iterator` or `std::vector::iterator` - a pair of pointers such as `ptr` and `ptr + len` +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance. + + - a custom sentinel type for C++20 ranges + - `std::counted_iterator` with a different sentinel type + ## Parameters `i` (in) @@ -61,7 +68,7 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep : iterator to the start of the character range `last` (in) -: iterator to the end of the character range +: iterator to the end of the character range, or a sentinel value that compares equal to the end iterator with `operator!=` ## Return value @@ -112,6 +119,7 @@ A UTF-8 byte order mark is silently ignored. - Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - Added `ignore_trailing_commas` in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index 8558e36ec..150f17aac 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -7,8 +7,8 @@ static basic_json from_bjdata(InputType&& i, const bool strict = true, const bool allow_exceptions = true); // (2) -template -static basic_json from_bjdata(IteratorType first, IteratorType last, +template +static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true); ``` @@ -16,7 +16,7 @@ static basic_json from_bjdata(IteratorType first, IteratorType last, Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. 1. Reads from a compatible input. -2. Reads from an iterator range. +2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md). @@ -35,6 +35,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a + custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +48,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : iterator to the start of the input `last` (in) -: iterator to the end of the input +: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` `strict` (in) : whether to expect the input to be consumed until EOF (`#!cpp true` by default) @@ -103,3 +107,4 @@ Linear in the size of the input. - Added in version 3.11.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index 7f08ca0bd..eb06a7177 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -7,8 +7,8 @@ static basic_json from_bson(InputType&& i, const bool strict = true, const bool allow_exceptions = true); // (2) -template -static basic_json from_bson(IteratorType first, IteratorType last, +template +static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true); ``` @@ -16,7 +16,7 @@ static basic_json from_bson(IteratorType first, IteratorType last, Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format. 1. Reads from a compatible input. -2. Reads from an iterator range. +2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md). @@ -35,6 +35,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a + custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +48,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : iterator to the start of the input `last` (in) -: iterator to the end of the input +: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` `strict` (in) : whether to expect the input to be consumed until EOF (`#!cpp true` by default) @@ -103,6 +107,7 @@ Linear in the size of the input. - Added in version 3.4.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index b736b8581..3792307bd 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -9,8 +9,8 @@ static basic_json from_cbor(InputType&& i, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); // (2) -template -static basic_json from_cbor(IteratorType first, IteratorType last, +template +static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); @@ -19,7 +19,7 @@ static basic_json from_cbor(IteratorType first, IteratorType last, Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. 1. Reads from a compatible input. -2. Reads from an iterator range. +2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md). @@ -38,6 +38,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a + custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -47,7 +51,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : iterator to the start of the input `last` (in) -: iterator to the end of the input +: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` `strict` (in) : whether to expect the input to be consumed until EOF (`#!cpp true` by default) @@ -113,6 +117,7 @@ Linear in the size of the input. - Added `allow_exceptions` parameter in version 3.2.0. - Added `tag_handler` parameter in version 3.9.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_msgpack.md b/docs/mkdocs/docs/api/basic_json/from_msgpack.md index 69f45605f..dd7719804 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -7,8 +7,8 @@ static basic_json from_msgpack(InputType&& i, const bool strict = true, const bool allow_exceptions = true); // (2) -template -static basic_json from_msgpack(IteratorType first, IteratorType last, +template +static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true); ``` @@ -16,7 +16,7 @@ static basic_json from_msgpack(IteratorType first, IteratorType last, Deserializes a given input to a JSON value using the MessagePack serialization format. 1. Reads from a compatible input. -2. Reads from an iterator range. +2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md). @@ -35,6 +35,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a + custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +48,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : iterator to the start of the input `last` (in) -: iterator to the end of the input +: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` `strict` (in) : whether to expect the input to be consumed until EOF (`#!cpp true` by default) @@ -105,6 +109,7 @@ Linear in the size of the input. - Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0. - Added `allow_exceptions` parameter in version 3.2.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index 82eb2d4de..55c68c0ae 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -7,8 +7,8 @@ static basic_json from_ubjson(InputType&& i, const bool strict = true, const bool allow_exceptions = true); // (2) -template -static basic_json from_ubjson(IteratorType first, IteratorType last, +template +static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true); ``` @@ -16,7 +16,7 @@ static basic_json from_ubjson(IteratorType first, IteratorType last, Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format. 1. Reads from a compatible input. -2. Reads from an iterator range. +2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md). @@ -35,6 +35,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a + custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +48,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : iterator to the start of the input `last` (in) -: iterator to the end of the input +: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` `strict` (in) : whether to expect the input to be consumed until EOF (`#!cpp true` by default) @@ -104,6 +108,7 @@ Linear in the size of the input. - Added in version 3.1.0. - Added `allow_exceptions` parameter in version 3.2.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/parse.md b/docs/mkdocs/docs/api/basic_json/parse.md index 4d202e8a9..4565c8762 100644 --- a/docs/mkdocs/docs/api/basic_json/parse.md +++ b/docs/mkdocs/docs/api/basic_json/parse.md @@ -10,8 +10,8 @@ static basic_json parse(InputType&& i, const bool ignore_trailing_commas = false); // (2) -template -static basic_json parse(IteratorType first, IteratorType last, +template +static basic_json parse(IteratorType first, SentinelType last, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false, @@ -19,10 +19,11 @@ static basic_json parse(IteratorType first, IteratorType last, ``` 1. Deserialize from a compatible input. -2. Deserialize from a pair of character iterators +2. Deserialize from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) The `value_type` of the iterator must be an integral type with size of 1, 2, or 4 bytes, which will be interpreted - respectively as UTF-8, UTF-16, and UTF-32. + respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to + the iterator type with `operator!=`. ## Template parameters @@ -43,6 +44,12 @@ static basic_json parse(IteratorType first, IteratorType last, - a pair of `std::string::iterator` or `std::vector::iterator` - a pair of pointers such as `ptr` and `ptr + len` +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance. + + - a custom sentinel type for C++20 ranges + - `std::counted_iterator` with a different sentinel type + ## Parameters `i` (in) @@ -67,7 +74,7 @@ static basic_json parse(IteratorType first, IteratorType last, : iterator to the start of a character range `last` (in) -: iterator to the end of a character range +: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=` ## Return value @@ -238,6 +245,7 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as - Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - Added `ignore_trailing_commas` in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/sax_parse.md b/docs/mkdocs/docs/api/basic_json/sax_parse.md index 81fa461a0..606ab9bbe 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -11,8 +11,8 @@ static bool sax_parse(InputType&& i, const bool ignore_trailing_commas = false); // (2) -template -static bool sax_parse(IteratorType first, IteratorType last, +template +static bool sax_parse(IteratorType first, SentinelType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, @@ -23,10 +23,11 @@ static bool sax_parse(IteratorType first, IteratorType last, Read from input and generate SAX events 1. Read from a compatible input. -2. Read from a pair of character iterators +2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted - respectively as UTF-8, UTF-16, and UTF-32. + respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to + the iterator type with `operator!=`. The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md). @@ -46,6 +47,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index : a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32) +`SentinelType` +: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for overload (2) + `SAX` : a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md) @@ -76,7 +80,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index : iterator to the start of a character range `last` (in) -: iterator to the end of a character range +: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=` ## Return value @@ -128,6 +132,7 @@ A UTF-8 byte order mark is silently ignored. - Ignoring comments via `ignore_comments` added in version 3.9.0. - Added `ignore_trailing_commas` in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. +- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. !!! warning "Deprecation" diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 52d221b54..1ef8a6af7 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -155,7 +155,9 @@ class input_stream_adapter // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. -template +// SentinelType defaults to IteratorType for backward compatibility, but may +// be a different type (e.g., a C++20 sentinel or counted_iterator). +template class iterator_input_adapter { public: @@ -169,9 +171,10 @@ class iterator_input_adapter // in wide_string_input_adapter, which does not expose this). static constexpr bool supports_seek = std::is_same::iterator_category, std::random_access_iterator_tag>::value + && std::is_same::value && sizeof(char_type) == 1; - iterator_input_adapter(IteratorType first, IteratorType last) + iterator_input_adapter(IteratorType first, SentinelType last) : begin(first), current(std::move(first)), end(std::move(last)) {} @@ -216,12 +219,14 @@ class iterator_input_adapter private: // whether IteratorType refers to a contiguous range and therefore supports // a std::memcpy fast path (pointers always do; in C++20 we can also detect - // library iterators such as those of std::vector and std::string) + // library iterators such as those of std::vector and std::string). + // The fast path also requires SentinelType == IteratorType so std::distance works. static constexpr bool iterator_is_contiguous = + std::is_same::value && ( #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) - std::contiguous_iterator || + std::contiguous_iterator || #endif - std::is_pointer::value; + std::is_pointer::value); // contiguous fast path: bulk copy the remaining range with std::memcpy template @@ -267,7 +272,7 @@ class iterator_input_adapter IteratorType begin; IteratorType current; - IteratorType end; + SentinelType end; template friend struct wide_string_input_helper; @@ -453,19 +458,54 @@ class wide_string_input_adapter std::size_t utf8_bytes_filled = 0; }; -template +template struct iterator_input_adapter_factory { using iterator_type = IteratorType; + using sentinel_type = SentinelType; using char_type = typename std::iterator_traits::value_type; - using adapter_type = iterator_input_adapter; + using adapter_type = iterator_input_adapter; - static adapter_type create(IteratorType first, IteratorType last) + static adapter_type create(IteratorType first, SentinelType last) { return adapter_type(std::move(first), std::move(last)); } }; +// Detection: whether IteratorType and SentinelType can be compared with != +template +struct can_compare_ne_impl : std::false_type {}; + +template +struct can_compare_ne_impl < IteratorType, SentinelType, + void_t < decltype(std::declval() != std::declval()) >> + : std::true_type {}; + +// Workaround for reversed operator order +template +struct can_compare_ne_reversed : std::false_type {}; + +template +struct can_compare_ne_reversed < IteratorType, SentinelType, + void_t < decltype(std::declval() != std::declval()) >> + : std::true_type {}; + +template +struct can_compare_ne_either_order : std::integral_constant < bool, + can_compare_ne_impl::value || + can_compare_ne_reversed::value > {}; + +// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a +// trailing default argument (e.g. parse(s, nullptr, ...)) must never be +// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably +// SFINAE the `operator!=` detection above for std::nullptr_t against +// container/string types, which would otherwise make such calls ambiguous +// with the compatible-input overload. +template +struct can_compare_ne : std::integral_constant < bool, + !std::is_same::value && + can_compare_ne_either_order::value > {}; + template struct is_iterator_of_multibyte { @@ -476,25 +516,31 @@ struct is_iterator_of_multibyte }; }; -template -struct iterator_input_adapter_factory::value>> +template +struct iterator_input_adapter_factory::value>> { using iterator_type = IteratorType; + using sentinel_type = SentinelType; using char_type = typename std::iterator_traits::value_type; - using base_adapter_type = iterator_input_adapter; + using base_adapter_type = iterator_input_adapter; using adapter_type = wide_string_input_adapter; - static adapter_type create(IteratorType first, IteratorType last) + static adapter_type create(IteratorType first, SentinelType last) { return adapter_type(base_adapter_type(std::move(first), std::move(last))); } }; -// General purpose iterator-based input -template -typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +// 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 +// be compared with !=. +template < typename IteratorType, typename SentinelType = IteratorType, + typename = typename std::enable_if < + can_compare_ne::value >::type > +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, SentinelType last) { - using factory_type = iterator_input_adapter_factory; + using factory_type = iterator_input_adapter_factory; return factory_type::create(first, last); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3d9fe8f5e..a460bb29f 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4083,12 +4083,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return result; } - /// @brief deserialize from a pair of character iterators + /// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/parse/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, - IteratorType last, + SentinelType last, parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false, @@ -4122,10 +4123,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } - /// @brief check if the input is valid JSON + /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/accept/ - template - static bool accept(IteratorType first, IteratorType last, + template::value, int> = 0> + static bool accept(IteratorType first, SentinelType last, const bool ignore_comments = false, const bool ignore_trailing_commas = false) { @@ -4157,11 +4159,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } - /// @brief generate SAX events + /// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ - template + template::value, int> = 0> JSON_HEDLEY_NON_NULL(3) - static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + static bool sax_parse(IteratorType first, SentinelType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, @@ -4461,11 +4464,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in CBOR format + /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_cbor(IteratorType first, IteratorType last, + static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) @@ -4518,11 +4522,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in MessagePack format + /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_msgpack(IteratorType first, IteratorType last, + static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -4572,11 +4577,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in UBJSON format + /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_ubjson(IteratorType first, IteratorType last, + static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -4626,11 +4632,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in BJData format + /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(IteratorType first, IteratorType last, + static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -4656,11 +4663,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in BSON format + /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bson(IteratorType first, IteratorType last, + static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6952210c1..99fceb63b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -7142,7 +7142,9 @@ class input_stream_adapter // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. -template +// SentinelType defaults to IteratorType for backward compatibility, but may +// be a different type (e.g., a C++20 sentinel or counted_iterator). +template class iterator_input_adapter { public: @@ -7156,9 +7158,10 @@ class iterator_input_adapter // in wide_string_input_adapter, which does not expose this). static constexpr bool supports_seek = std::is_same::iterator_category, std::random_access_iterator_tag>::value + && std::is_same::value && sizeof(char_type) == 1; - iterator_input_adapter(IteratorType first, IteratorType last) + iterator_input_adapter(IteratorType first, SentinelType last) : begin(first), current(std::move(first)), end(std::move(last)) {} @@ -7203,12 +7206,14 @@ class iterator_input_adapter private: // whether IteratorType refers to a contiguous range and therefore supports // a std::memcpy fast path (pointers always do; in C++20 we can also detect - // library iterators such as those of std::vector and std::string) + // library iterators such as those of std::vector and std::string). + // The fast path also requires SentinelType == IteratorType so std::distance works. static constexpr bool iterator_is_contiguous = + std::is_same::value && ( #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) - std::contiguous_iterator || + std::contiguous_iterator || #endif - std::is_pointer::value; + std::is_pointer::value); // contiguous fast path: bulk copy the remaining range with std::memcpy template @@ -7254,7 +7259,7 @@ class iterator_input_adapter IteratorType begin; IteratorType current; - IteratorType end; + SentinelType end; template friend struct wide_string_input_helper; @@ -7440,19 +7445,54 @@ class wide_string_input_adapter std::size_t utf8_bytes_filled = 0; }; -template +template struct iterator_input_adapter_factory { using iterator_type = IteratorType; + using sentinel_type = SentinelType; using char_type = typename std::iterator_traits::value_type; - using adapter_type = iterator_input_adapter; + using adapter_type = iterator_input_adapter; - static adapter_type create(IteratorType first, IteratorType last) + static adapter_type create(IteratorType first, SentinelType last) { return adapter_type(std::move(first), std::move(last)); } }; +// Detection: whether IteratorType and SentinelType can be compared with != +template +struct can_compare_ne_impl : std::false_type {}; + +template +struct can_compare_ne_impl < IteratorType, SentinelType, + void_t < decltype(std::declval() != std::declval()) >> + : std::true_type {}; + +// Workaround for reversed operator order +template +struct can_compare_ne_reversed : std::false_type {}; + +template +struct can_compare_ne_reversed < IteratorType, SentinelType, + void_t < decltype(std::declval() != std::declval()) >> + : std::true_type {}; + +template +struct can_compare_ne_either_order : std::integral_constant < bool, + can_compare_ne_impl::value || + can_compare_ne_reversed::value > {}; + +// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a +// trailing default argument (e.g. parse(s, nullptr, ...)) must never be +// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably +// SFINAE the `operator!=` detection above for std::nullptr_t against +// container/string types, which would otherwise make such calls ambiguous +// with the compatible-input overload. +template +struct can_compare_ne : std::integral_constant < bool, + !std::is_same::value && + can_compare_ne_either_order::value > {}; + template struct is_iterator_of_multibyte { @@ -7463,25 +7503,31 @@ struct is_iterator_of_multibyte }; }; -template -struct iterator_input_adapter_factory::value>> +template +struct iterator_input_adapter_factory::value>> { using iterator_type = IteratorType; + using sentinel_type = SentinelType; using char_type = typename std::iterator_traits::value_type; - using base_adapter_type = iterator_input_adapter; + using base_adapter_type = iterator_input_adapter; using adapter_type = wide_string_input_adapter; - static adapter_type create(IteratorType first, IteratorType last) + static adapter_type create(IteratorType first, SentinelType last) { return adapter_type(base_adapter_type(std::move(first), std::move(last))); } }; -// General purpose iterator-based input -template -typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +// 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 +// be compared with !=. +template < typename IteratorType, typename SentinelType = IteratorType, + typename = typename std::enable_if < + can_compare_ne::value >::type > +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, SentinelType last) { - using factory_type = iterator_input_adapter_factory; + using factory_type = iterator_input_adapter_factory; return factory_type::create(first, last); } @@ -25084,12 +25130,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return result; } - /// @brief deserialize from a pair of character iterators + /// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/parse/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, - IteratorType last, + SentinelType last, parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false, @@ -25123,10 +25170,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } - /// @brief check if the input is valid JSON + /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/accept/ - template - static bool accept(IteratorType first, IteratorType last, + template::value, int> = 0> + static bool accept(IteratorType first, SentinelType last, const bool ignore_comments = false, const bool ignore_trailing_commas = false) { @@ -25158,11 +25206,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } - /// @brief generate SAX events + /// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ - template + template::value, int> = 0> JSON_HEDLEY_NON_NULL(3) - static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + static bool sax_parse(IteratorType first, SentinelType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, @@ -25462,11 +25511,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in CBOR format + /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_cbor(IteratorType first, IteratorType last, + static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) @@ -25519,11 +25569,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in MessagePack format + /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_msgpack(IteratorType first, IteratorType last, + static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -25573,11 +25624,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in UBJSON format + /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_ubjson(IteratorType first, IteratorType last, + static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -25627,11 +25679,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in BJData format + /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(IteratorType first, IteratorType last, + static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { @@ -25657,11 +25710,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - /// @brief create a JSON value from an input in BSON format + /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ - template + template::value, int> = 0> JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bson(IteratorType first, IteratorType last, + static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true) { diff --git a/tests/src/test_utils.hpp b/tests/src/test_utils.hpp index 37afa21e8..baa802f71 100644 --- a/tests/src/test_utils.hpp +++ b/tests/src/test_utils.hpp @@ -30,4 +30,17 @@ inline std::vector read_binary_file(const std::string& filename) return byte_vector; } +// sentinel for istreambuf_iterator; compares != true until EOF is reached +// lets tests read a file directly via the new iterator+sentinel overloads +// instead of buffering the whole file into a vector first. +// Only the iterator-first direction (it != sentinel) is ever evaluated by +// the library's parse loop, so no reversed-order overload is needed. +struct istreambuf_sentinel +{ + friend bool operator!=(const std::istreambuf_iterator& it, const istreambuf_sentinel& /*unused*/) noexcept + { + return it != std::istreambuf_iterator(); + } +}; + } // namespace utils diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index c8459315d..5af4afac1 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3699,6 +3699,15 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") } } +TEST_CASE("Parse BJData directly from a file using iterator and sentinel") +{ + std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bjdata"; + std::ifstream file(filename, std::ios::binary); + const std::istreambuf_iterator first(file); + const json parsed = json::from_bjdata(first, utils::istreambuf_sentinel{}); + CHECK((parsed.is_object() || parsed.is_array())); +} + #if !defined(JSON_NOEXCEPTION) TEST_CASE("all BJData first bytes") { diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index eed70352e..28837d75e 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1208,6 +1208,19 @@ TEST_CASE("BSON numerical data") } } +TEST_CASE("Parse BSON directly from a file using iterator and sentinel") +{ + std::string const filename = TEST_DATA_DIRECTORY "/json.org/1.json"; + + std::ifstream f_json(filename); + const json expected = json::parse(f_json); + + std::ifstream file(filename + ".bson", std::ios::binary); + const std::istreambuf_iterator first(file); + const json parsed = json::from_bson(first, utils::istreambuf_sentinel{}); + CHECK(parsed == expected); +} + TEST_CASE("BSON roundtrips" * doctest::skip()) { SECTION("reference files") diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 52c808027..af7364299 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -1921,6 +1921,15 @@ TEST_CASE("single CBOR roundtrip") } } +TEST_CASE("Parse CBOR directly from a file using iterator and sentinel") +{ + std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.cbor"; + std::ifstream file(filename, std::ios::binary); + const std::istreambuf_iterator first(file); + const json parsed = json::from_cbor(first, utils::istreambuf_sentinel{}); + CHECK((parsed.is_object() || parsed.is_array())); +} + #if !defined(JSON_NOEXCEPTION) TEST_CASE("CBOR regressions") { diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 02fb7a2e4..4358cd464 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -1641,6 +1641,15 @@ TEST_CASE("single MessagePack roundtrip") } } +TEST_CASE("Parse MessagePack directly from a file using iterator and sentinel") +{ + std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.msgpack"; + std::ifstream file(filename, std::ios::binary); + const std::istreambuf_iterator first(file); + const json parsed = json::from_msgpack(first, utils::istreambuf_sentinel{}); + CHECK((parsed.is_object() || parsed.is_array())); +} + TEST_CASE("MessagePack roundtrips" * doctest::skip()) { SECTION("input from msgpack-python") diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 99d07d966..8242a0cc8 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2393,6 +2393,15 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") } } +TEST_CASE("Parse UBJSON directly from a file using iterator and sentinel") +{ + std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.ubjson"; + std::ifstream file(filename, std::ios::binary); + const std::istreambuf_iterator first(file); + const json parsed = json::from_ubjson(first, utils::istreambuf_sentinel{}); + CHECK((parsed.is_object() || parsed.is_array())); +} + #if !defined(JSON_NOEXCEPTION) TEST_CASE("all UBJSON first bytes") { diff --git a/tests/src/unit-user_defined_input.cpp b/tests/src/unit-user_defined_input.cpp index 1a9b6394b..cb93439e5 100644 --- a/tests/src/unit-user_defined_input.cpp +++ b/tests/src/unit-user_defined_input.cpp @@ -168,4 +168,37 @@ TEST_CASE("Custom iterator") CHECK(as_json.at(3) == 4); } +// Custom sentinel type for testing heterogeneous iterator+sentinel support +struct CustomSentinel +{ + const char* end_ptr; + + // only the iterator-first direction (it != sentinel) is ever evaluated by + // the library's parse loop; a reversed-order overload would go unused and + // trip -Wunneeded-internal-declaration under -Weverything + friend bool operator!=(const char* it, const CustomSentinel& sentinel) + { + return it != sentinel.end_ptr; + } +}; + +TEST_CASE("Parse with heterogeneous iterator and sentinel types") +{ + const std::string json_str = R"({"key":"value"})"; + const char* end_ptr = json_str.data() + json_str.size(); + + // Parse using pointer and sentinel (different types) + json j = json::parse(json_str.data(), CustomSentinel{end_ptr}); + CHECK(j["key"] == "value"); + + // Accept using pointer and sentinel + CHECK(json::accept(json_str.data(), CustomSentinel{end_ptr})); + + // Test that the same-type case still works + std::string raw_data = R"([1,2,3])"; + std::list data(raw_data.begin(), raw_data.end()); + json j2 = json::parse(data.begin(), data.end()); + CHECK(j2.at(0) == 1); +} + } // namespace