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