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<char> + 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 <mail@nlohmann.me>
* 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 <mail@nlohmann.me>
* 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 <mail@nlohmann.me>
* 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 <mail@nlohmann.me>
* 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 <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
diff --git a/docs/mkdocs/docs/api/basic_json/accept.md b/docs/mkdocs/docs/api/basic_json/accept.md
index df23971..dac5f9f 100644
--- a/docs/mkdocs/docs/api/basic_json/accept.md
+++ b/docs/mkdocs/docs/api/basic_json/accept.md
@@ -8,8 +8,8 @@
const bool ignore_trailing_commas = false);
// (2)
-template<typename IteratorType>
-static bool accept(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false);
```
@@ -17,10 +17,11 @@
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 @@
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::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 @@
: 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 @@
- 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 8558e36..150f17a 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 @@
const bool strict = true,
const bool allow_exceptions = true);
// (2)
-template<typename IteratorType>
-static basic_json from_bjdata(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+static basic_json from_bjdata(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true);
```
@@ -16,7 +16,7 @@
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 @@
`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 @@
: 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 @@
- 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 7f08ca0..eb06a71 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 @@
const bool strict = true,
const bool allow_exceptions = true);
// (2)
-template<typename IteratorType>
-static basic_json from_bson(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+static basic_json from_bson(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true);
```
@@ -16,7 +16,7 @@
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 @@
`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 @@
: 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 @@
- 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 b736b85..3792307 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 @@
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
// (2)
-template<typename IteratorType>
-static basic_json from_cbor(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+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 @@
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 @@
`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 @@
: 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 @@
- 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 69f4560..dd77198 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 @@
const bool strict = true,
const bool allow_exceptions = true);
// (2)
-template<typename IteratorType>
-static basic_json from_msgpack(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+static basic_json from_msgpack(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true);
```
@@ -16,7 +16,7 @@
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 @@
`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 @@
: 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 @@
- 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 82eb2d4..55c68c0 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 @@
const bool strict = true,
const bool allow_exceptions = true);
// (2)
-template<typename IteratorType>
-static basic_json from_ubjson(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+static basic_json from_ubjson(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true);
```
@@ -16,7 +16,7 @@
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 @@
`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 @@
: 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 @@
- 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 4d202e8..4565c87 100644
--- a/docs/mkdocs/docs/api/basic_json/parse.md
+++ b/docs/mkdocs/docs/api/basic_json/parse.md
@@ -10,8 +10,8 @@
const bool ignore_trailing_commas = false);
// (2)
-template<typename IteratorType>
-static basic_json parse(IteratorType first, IteratorType last,
+template<typename IteratorType, typename SentinelType = IteratorType>
+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 @@
```
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 @@
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::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 @@
: 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 @@
- 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 81fa461..606ab9b 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 @@
const bool ignore_trailing_commas = false);
// (2)
-template<class IteratorType, class SAX>
-static bool sax_parse(IteratorType first, IteratorType last,
+template<class IteratorType, class SAX, class SentinelType = IteratorType>
+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 @@
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 @@
: 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 @@
: 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 @@
- 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 52d221b..1ef8a6a 100644
--- a/include/nlohmann/detail/input/input_adapters.hpp
+++ b/include/nlohmann/detail/input/input_adapters.hpp
@@ -155,7 +155,9 @@
// General-purpose iterator-based adapter. It might not be as fast as
// theoretically possible for some containers, but it is extremely versatile.
-template<typename IteratorType>
+// SentinelType defaults to IteratorType for backward compatibility, but may
+// be a different type (e.g., a C++20 sentinel or counted_iterator).
+template<typename IteratorType, typename SentinelType = IteratorType>
class iterator_input_adapter
{
public:
@@ -169,9 +171,10 @@
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
+ && std::is_same<IteratorType, SentinelType>::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 @@
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<IteratorType, SentinelType>::value && (
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
- std::contiguous_iterator<IteratorType> ||
+ std::contiguous_iterator<IteratorType> ||
#endif
- std::is_pointer<IteratorType>::value;
+ std::is_pointer<IteratorType>::value);
// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
@@ -267,7 +272,7 @@
IteratorType begin;
IteratorType current;
- IteratorType end;
+ SentinelType end;
template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper;
@@ -453,19 +458,54 @@
std::size_t utf8_bytes_filled = 0;
};
-template<typename IteratorType, typename Enable = void>
+template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void>
struct iterator_input_adapter_factory
{
using iterator_type = IteratorType;
+ using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
- using adapter_type = iterator_input_adapter<iterator_type>;
+ using adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
- 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<typename IteratorType, typename SentinelType, typename = void>
+struct can_compare_ne_impl : std::false_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_impl < IteratorType, SentinelType,
+ void_t < decltype(std::declval<IteratorType>() != std::declval<SentinelType>()) >>
+ : std::true_type {};
+
+// Workaround for reversed operator order
+template<typename IteratorType, typename SentinelType, typename = void>
+struct can_compare_ne_reversed : std::false_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_reversed < IteratorType, SentinelType,
+ void_t < decltype(std::declval<SentinelType>() != std::declval<IteratorType>()) >>
+ : std::true_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_either_order : std::integral_constant < bool,
+ can_compare_ne_impl<IteratorType, SentinelType>::value ||
+ can_compare_ne_reversed<IteratorType, SentinelType>::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<typename IteratorType, typename SentinelType>
+struct can_compare_ne : std::integral_constant < bool,
+ !std::is_same<SentinelType, std::nullptr_t>::value &&
+ can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
+
template<typename T>
struct is_iterator_of_multibyte
{
@@ -476,25 +516,31 @@
};
};
-template<typename IteratorType>
-struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
+template<typename IteratorType, typename SentinelType>
+struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{
using iterator_type = IteratorType;
+ using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
- using base_adapter_type = iterator_input_adapter<iterator_type>;
+ using base_adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
- 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 IteratorType>
-typename iterator_input_adapter_factory<IteratorType>::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<IteratorType, SentinelType>::value >::type >
+typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_type input_adapter(IteratorType first, SentinelType last)
{
- using factory_type = iterator_input_adapter_factory<IteratorType>;
+ using factory_type = iterator_input_adapter_factory<IteratorType, SentinelType>;
return factory_type::create(first, last);
}
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 3d9fe8f..a460bb2 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -4083,12 +4083,13 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
return parser(detail::input_adapter(std::forward<InputType>(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<typename IteratorType>
- static bool accept(IteratorType first, IteratorType last,
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
: detail::binary_reader<basic_json, decltype(ia), SAX>(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<class IteratorType, class SAX>
+ template<class IteratorType, class SAX, class SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 6952210..99fceb6 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -7142,7 +7142,9 @@
// General-purpose iterator-based adapter. It might not be as fast as
// theoretically possible for some containers, but it is extremely versatile.
-template<typename IteratorType>
+// SentinelType defaults to IteratorType for backward compatibility, but may
+// be a different type (e.g., a C++20 sentinel or counted_iterator).
+template<typename IteratorType, typename SentinelType = IteratorType>
class iterator_input_adapter
{
public:
@@ -7156,9 +7158,10 @@
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
+ && std::is_same<IteratorType, SentinelType>::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 @@
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<IteratorType, SentinelType>::value && (
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
- std::contiguous_iterator<IteratorType> ||
+ std::contiguous_iterator<IteratorType> ||
#endif
- std::is_pointer<IteratorType>::value;
+ std::is_pointer<IteratorType>::value);
// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
@@ -7254,7 +7259,7 @@
IteratorType begin;
IteratorType current;
- IteratorType end;
+ SentinelType end;
template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper;
@@ -7440,19 +7445,54 @@
std::size_t utf8_bytes_filled = 0;
};
-template<typename IteratorType, typename Enable = void>
+template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void>
struct iterator_input_adapter_factory
{
using iterator_type = IteratorType;
+ using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
- using adapter_type = iterator_input_adapter<iterator_type>;
+ using adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
- 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<typename IteratorType, typename SentinelType, typename = void>
+struct can_compare_ne_impl : std::false_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_impl < IteratorType, SentinelType,
+ void_t < decltype(std::declval<IteratorType>() != std::declval<SentinelType>()) >>
+ : std::true_type {};
+
+// Workaround for reversed operator order
+template<typename IteratorType, typename SentinelType, typename = void>
+struct can_compare_ne_reversed : std::false_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_reversed < IteratorType, SentinelType,
+ void_t < decltype(std::declval<SentinelType>() != std::declval<IteratorType>()) >>
+ : std::true_type {};
+
+template<typename IteratorType, typename SentinelType>
+struct can_compare_ne_either_order : std::integral_constant < bool,
+ can_compare_ne_impl<IteratorType, SentinelType>::value ||
+ can_compare_ne_reversed<IteratorType, SentinelType>::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<typename IteratorType, typename SentinelType>
+struct can_compare_ne : std::integral_constant < bool,
+ !std::is_same<SentinelType, std::nullptr_t>::value &&
+ can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
+
template<typename T>
struct is_iterator_of_multibyte
{
@@ -7463,25 +7503,31 @@
};
};
-template<typename IteratorType>
-struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
+template<typename IteratorType, typename SentinelType>
+struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{
using iterator_type = IteratorType;
+ using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
- using base_adapter_type = iterator_input_adapter<iterator_type>;
+ using base_adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
- 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 IteratorType>
-typename iterator_input_adapter_factory<IteratorType>::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<IteratorType, SentinelType>::value >::type >
+typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_type input_adapter(IteratorType first, SentinelType last)
{
- using factory_type = iterator_input_adapter_factory<IteratorType>;
+ using factory_type = iterator_input_adapter_factory<IteratorType, SentinelType>;
return factory_type::create(first, last);
}
@@ -25084,12 +25130,13 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
return parser(detail::input_adapter(std::forward<InputType>(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<typename IteratorType>
- static bool accept(IteratorType first, IteratorType last,
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
: detail::binary_reader<basic_json, decltype(ia), SAX>(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<class IteratorType, class SAX>
+ template<class IteratorType, class SAX, class SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 @@
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<typename IteratorType>
+ template<typename IteratorType, typename SentinelType = IteratorType,
+ detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::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 37afa21..baa802f 100644
--- a/tests/src/test_utils.hpp
+++ b/tests/src/test_utils.hpp
@@ -30,4 +30,17 @@
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<char>& it, const istreambuf_sentinel& /*unused*/) noexcept
+ {
+ return it != std::istreambuf_iterator<char>();
+ }
+};
+
} // namespace utils
diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp
index c845931..5af4afa 100644
--- a/tests/src/unit-bjdata.cpp
+++ b/tests/src/unit-bjdata.cpp
@@ -3699,6 +3699,15 @@
}
}
+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<char> 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 eed7035..28837d7 100644
--- a/tests/src/unit-bson.cpp
+++ b/tests/src/unit-bson.cpp
@@ -1208,6 +1208,19 @@
}
}
+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<char> 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 52c8080..af73642 100644
--- a/tests/src/unit-cbor.cpp
+++ b/tests/src/unit-cbor.cpp
@@ -1921,6 +1921,15 @@
}
}
+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<char> 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 02fb7a2..4358cd4 100644
--- a/tests/src/unit-msgpack.cpp
+++ b/tests/src/unit-msgpack.cpp
@@ -1641,6 +1641,15 @@
}
}
+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<char> 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 99d07d9..8242a0c 100644
--- a/tests/src/unit-ubjson.cpp
+++ b/tests/src/unit-ubjson.cpp
@@ -2393,6 +2393,15 @@
}
}
+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<char> 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 1a9b639..cb93439 100644
--- a/tests/src/unit-user_defined_input.cpp
+++ b/tests/src/unit-user_defined_input.cpp
@@ -168,4 +168,37 @@
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<char> data(raw_data.begin(), raw_data.end());
+ json j2 = json::parse(data.begin(), data.end());
+ CHECK(j2.at(0) == 1);
+}
+
} // namespace