From 2269656bc6c21ad91c2f93b7a3d89a90157dc781 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 10 Jul 2026 19:43:05 +0200 Subject: [PATCH] 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 --- docs/mkdocs/docs/api/basic_json/accept.md | 21 +- .../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 | 14 +- .../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 | 23 +- docs/mkdocs/docs/api/basic_json/sax_parse.md | 23 +- .../nlohmann/detail/input/input_adapters.hpp | 74 +++++-- include/nlohmann/json.hpp | 134 +++++++++++ single_include/nlohmann/json.hpp | 208 ++++++++++++++++-- tests/src/test_utils.hpp | 16 ++ tests/src/unit-bjdata.cpp | 9 + tests/src/unit-bson.cpp | 19 ++ 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 | 36 +++ 18 files changed, 612 insertions(+), 44 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/accept.md b/docs/mkdocs/docs/api/basic_json/accept.md index df239713f..77100a045 100644 --- a/docs/mkdocs/docs/api/basic_json/accept.md +++ b/docs/mkdocs/docs/api/basic_json/accept.md @@ -12,15 +12,25 @@ template static bool accept(IteratorType first, IteratorType last, const bool ignore_comments = false, const bool ignore_trailing_commas = false); + +// (3) +template +static bool accept(IteratorType first, SentinelType last, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false); ``` 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 (same type) 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. +3. Reads from an iterator and a sentinel (different types, 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. The sentinel type 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 +54,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` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable 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 +77,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 +128,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. +- Added overload (3) for 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..9c6768ef3 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -11,12 +11,19 @@ template static basic_json from_bjdata(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true); + +// (3) +template +static basic_json from_bjdata(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true); ``` 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. +3. Reads from an iterator and a sentinel (different types, C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md). @@ -35,6 +42,9 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`), for instance a custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +54,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 +113,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. +- Added overload (3) for 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..5d9967c42 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -11,12 +11,19 @@ template static basic_json from_bson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true); + +// (3) +template +static basic_json from_bson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true); ``` 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. +3. Reads from an iterator and a sentinel (different types, C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md). @@ -35,6 +42,9 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`), for instance a custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +54,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 +113,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. +- Added overload (3) for 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..c9a326c96 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -14,12 +14,20 @@ static basic_json from_cbor(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); + +// (3) +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); ``` 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. +3. Reads from an iterator and a sentinel (different types, C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md). @@ -38,6 +46,9 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`), for instance a custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -47,7 +58,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 +124,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. +- Added overload (3) for 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..21876af72 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -11,12 +11,19 @@ template static basic_json from_msgpack(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true); + +// (3) +template +static basic_json from_msgpack(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true); ``` 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. +3. Reads from an iterator and a sentinel (different types, C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md). @@ -35,6 +42,9 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`), for instance a custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +54,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 +115,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. +- Added overload (3) for 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..8a2d814ba 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -11,12 +11,19 @@ template static basic_json from_ubjson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true); + +// (3) +template +static basic_json from_ubjson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true); ``` 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. +3. Reads from an iterator and a sentinel (different types, C++20 ranges support). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md). @@ -35,6 +42,9 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `IteratorType` : a compatible iterator type +`SentinelType` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`), for instance a custom sentinel type for C++20 ranges + ## Parameters `i` (in) @@ -44,7 +54,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 +114,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. +- Added overload (3) for 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..3af405cb1 100644 --- a/docs/mkdocs/docs/api/basic_json/parse.md +++ b/docs/mkdocs/docs/api/basic_json/parse.md @@ -16,13 +16,25 @@ static basic_json parse(IteratorType first, IteratorType last, const bool allow_exceptions = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false); + +// (3) +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, + const bool ignore_trailing_commas = false); ``` 1. Deserialize from a compatible input. -2. Deserialize from a pair of character iterators +2. Deserialize from a pair of character iterators (same type) 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. +3. Deserialize from an iterator and a sentinel (different types, 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. The sentinel type must be comparable to the iterator type with `operator!=`. ## Template parameters @@ -43,6 +55,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` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable 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 +85,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 +256,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. +- Added overload (3) for 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..d7da30311 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -18,15 +18,28 @@ static bool sax_parse(IteratorType first, IteratorType last, const bool strict = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false); + +// (3) +template +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, + const bool ignore_trailing_commas = false); ``` 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 (same type) 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. +3. Read from an iterator and a sentinel (different types, 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. The sentinel type must be comparable to the iterator type with `operator!=`. The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md). @@ -43,9 +56,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index (as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`) `IteratorType` -: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type +: a compatible iterator type for overloads (2) and (3); 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` +: a sentinel type compatible with `IteratorType` (different from `IteratorType` and comparable via `operator!=`) for overload (3) + `SAX` : a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md) @@ -76,7 +92,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 +144,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. +- Added overload (3) for 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..d646d228c 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,43 @@ 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 : std::integral_constant < bool, + can_compare_ne_impl::value || + can_compare_ne_reversed::value > {}; + template struct is_iterator_of_multibyte { @@ -476,25 +505,38 @@ 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 +// General purpose iterator-based input (same-type iterators) template -typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) { - using factory_type = iterator_input_adapter_factory; + using factory_type = iterator_input_adapter_factory; + return factory_type::create(first, last); +} + +// General purpose iterator-based input (iterator+sentinel pair with different types) +// Only enable for types that can be compared with != +template < typename IteratorType, typename SentinelType, + typename = typename std::enable_if < + !std::is_same::value && + 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; return factory_type::create(first, last); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3d9fe8f5e..65ab0113a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4099,6 +4099,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return result; } + /// @brief deserialize from an iterator+sentinel pair (C++20 ranges support) + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + SentinelType last, + parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + return result; + } + JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, @@ -4132,6 +4150,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } + /// @brief check if the input is valid JSON (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template + static bool accept(IteratorType first, SentinelType last, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); + } + JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, @@ -4173,6 +4203,24 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template + JSON_HEDLEY_NON_NULL(3) + 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, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); + } + /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ /// @deprecated This function is deprecated since 3.8.0 and will be removed in @@ -4477,6 +4525,24 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + 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, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) @@ -4533,6 +4599,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) @@ -4587,6 +4670,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) @@ -4641,6 +4741,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -4671,6 +4788,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5395609f0..06cda9a69 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6992,7 +6992,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: @@ -7006,9 +7008,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)) {} @@ -7053,12 +7056,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 @@ -7104,7 +7109,7 @@ class iterator_input_adapter IteratorType begin; IteratorType current; - IteratorType end; + SentinelType end; template friend struct wide_string_input_helper; @@ -7290,19 +7295,43 @@ 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 : std::integral_constant < bool, + can_compare_ne_impl::value || + can_compare_ne_reversed::value > {}; + template struct is_iterator_of_multibyte { @@ -7313,25 +7342,38 @@ 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 +// General purpose iterator-based input (same-type iterators) template -typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) { - using factory_type = iterator_input_adapter_factory; + using factory_type = iterator_input_adapter_factory; + return factory_type::create(first, last); +} + +// General purpose iterator-based input (iterator+sentinel pair with different types) +// Only enable for types that can be compared with != +template < typename IteratorType, typename SentinelType, + typename = typename std::enable_if < + !std::is_same::value && + 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; return factory_type::create(first, last); } @@ -24950,6 +24992,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return result; } + /// @brief deserialize from an iterator+sentinel pair (C++20 ranges support) + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + SentinelType last, + parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + return result; + } + JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, @@ -24983,6 +25043,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } + /// @brief check if the input is valid JSON (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template + static bool accept(IteratorType first, SentinelType last, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); + } + JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, @@ -25024,6 +25096,24 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template + JSON_HEDLEY_NON_NULL(3) + 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, + const bool ignore_trailing_commas = false, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); + } + /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ /// @deprecated This function is deprecated since 3.8.0 and will be removed in @@ -25328,6 +25418,24 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + 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, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) @@ -25384,6 +25492,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) @@ -25438,6 +25563,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) @@ -25492,6 +25634,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -25522,6 +25681,23 @@ 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 (sentinel version) + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, SentinelType last, + const bool strict = true, + const bool allow_exceptions = true, + typename std::enable_if < !std::is_same::value && + detail::can_compare_ne::value >::type* = nullptr) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) diff --git a/tests/src/test_utils.hpp b/tests/src/test_utils.hpp index 37afa21e8..c6371e6e9 100644 --- a/tests/src/test_utils.hpp +++ b/tests/src/test_utils.hpp @@ -30,4 +30,20 @@ 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 +struct istreambuf_sentinel +{ + friend bool operator!=(const std::istreambuf_iterator& it, const istreambuf_sentinel&) noexcept + { + return it != std::istreambuf_iterator(); + } + + friend bool operator!=(const istreambuf_sentinel&, const std::istreambuf_iterator& it) noexcept + { + return it != std::istreambuf_iterator(); + } +}; + } // namespace utils diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index c8459315d..3c272236f 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); + 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..289f1f439 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1208,6 +1208,25 @@ TEST_CASE("BSON numerical data") } } +TEST_CASE("Parse BSON directly from a file using iterator and sentinel") +{ + const json j = {{"key", "value"}, {"number", 42}}; + std::vector bson_bytes; + json::to_bson(j, bson_bytes); + + { + std::ofstream file("test_bson_sentinel.bson", std::ios::binary); + file.write(reinterpret_cast(bson_bytes.data()), static_cast(bson_bytes.size())); + } + + std::ifstream file("test_bson_sentinel.bson", std::ios::binary); + std::istreambuf_iterator first(file); + const json parsed = json::from_bson(first, utils::istreambuf_sentinel{}); + CHECK(parsed == j); + + static_cast(std::remove("test_bson_sentinel.bson")); +} + 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..c0cd5cde3 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); + 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..d35fe4198 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); + 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..9dae264ed 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); + 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..7c6d5b746 100644 --- a/tests/src/unit-user_defined_input.cpp +++ b/tests/src/unit-user_defined_input.cpp @@ -168,4 +168,40 @@ 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; + + // Support both directions for != comparison + friend bool operator!=(const char* it, const CustomSentinel& sentinel) + { + return it != sentinel.end_ptr; + } + + friend bool operator!=(const CustomSentinel& sentinel, const char* it) + { + return it != sentinel.end_ptr; + } +}; + +TEST_CASE("Parse with heterogeneous iterator and sentinel types") +{ + 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