Compare commits

...

6 Commits

Author SHA1 Message Date
Niels Lohmann 0e03ecee10 Extend memcpy fast path to sized sentinels (e.g. std::counted_iterator)
Extend the memcpy fast path in iterator_input_adapter to sized
sentinels of a different type, not just same-type iterator pairs.
std::counted_iterator paired with std::default_sentinel_t already
satisfies std::contiguous_iterator and std::sized_sentinel_for, so
std::ranges::distance (C++20) lets that combination reach the fast
path too, instead of silently falling back to the byte-by-byte path.

- iterator_is_contiguous now also allows std::sized_sentinel_for<SentinelType,
  IteratorType> under C++20, gated the same way as the existing
  std::contiguous_iterator detection.
- get_elements_impl's fast path uses std::ranges::distance under C++20
  (works for both same-type and sized-sentinel pairs) and falls back to
  std::distance pre-C++20, where SentinelType is always IteratorType.
- Add a C++20-only test exercising json::parse/accept with
  std::counted_iterator + std::default_sentinel_t.
- Document std::default_sentinel_t + std::counted_iterator as a
  SentinelType example across parse.md, accept.md, sax_parse.md, and
  the five from_*.md pages, replacing an earlier ambiguously worded
  bullet.

Addresses review feedback: https://github.com/nlohmann/json/pull/5265#discussion_r3564237584

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 16:26:09 +02:00
Niels Lohmann d23806a327 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>
2026-07-11 10:06:11 +02:00
Niels Lohmann fa9741f0ce 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>
2026-07-11 00:10:23 +02:00
Niels Lohmann da95d1184e 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>
2026-07-10 23:25:21 +02:00
Niels Lohmann 146e5e0bc7 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>
2026-07-10 23:15:17 +02:00
Niels Lohmann 2269656bc6 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>
2026-07-10 19:50:04 +02:00
18 changed files with 425 additions and 117 deletions
+13 -5
View File
@@ -8,8 +8,8 @@ static bool accept(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static bool accept(IteratorType first, IteratorType last, static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
``` ```
@@ -17,10 +17,11 @@ static bool accept(IteratorType first, IteratorType last,
Checks whether the input is valid JSON. Checks whether the input is valid JSON.
1. Reads from a compatible input. 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 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 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. (i.e., a parse error) nor creates diagnostic information.
@@ -44,6 +45,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator` - a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
- a pair of pointers such as `ptr` and `ptr + len` - 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::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -61,7 +68,7 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
: iterator to the start of the character range : iterator to the start of the character range
`last` (in) `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 ## Return value
@@ -112,6 +119,7 @@ A UTF-8 byte order mark is silently ignored.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - 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. - 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 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" !!! warning "Deprecation"
+11 -4
View File
@@ -7,8 +7,8 @@ static basic_json from_bjdata(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_bjdata(IteratorType first, IteratorType last, static basic_json from_bjdata(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_bjdata(IteratorType first, IteratorType last,
Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format.
1. Reads from a compatible input. 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). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md).
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : 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
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `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) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -103,3 +109,4 @@ Linear in the size of the input.
- Added in version 3.11.0. - 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 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.
+11 -4
View File
@@ -7,8 +7,8 @@ static basic_json from_bson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_bson(IteratorType first, IteratorType last, static basic_json from_bson(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_bson(IteratorType first, IteratorType last,
Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format. Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format.
1. Reads from a compatible input. 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). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md).
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : 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
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `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) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -103,6 +109,7 @@ Linear in the size of the input.
- Added in version 3.4.0. - 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 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" !!! warning "Deprecation"
+11 -4
View File
@@ -9,8 +9,8 @@ static basic_json from_cbor(InputType&& i,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_cbor(IteratorType first, IteratorType last, static basic_json from_cbor(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
@@ -19,7 +19,7 @@ static basic_json from_cbor(IteratorType first, IteratorType last,
Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format.
1. Reads from a compatible input. 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). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md).
@@ -38,6 +38,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : 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
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -47,7 +53,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `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) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -113,6 +119,7 @@ Linear in the size of the input.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- Added `tag_handler` parameter in version 3.9.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 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" !!! warning "Deprecation"
@@ -7,8 +7,8 @@ static basic_json from_msgpack(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_msgpack(IteratorType first, IteratorType last, static basic_json from_msgpack(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_msgpack(IteratorType first, IteratorType last,
Deserializes a given input to a JSON value using the MessagePack serialization format. Deserializes a given input to a JSON value using the MessagePack serialization format.
1. Reads from a compatible input. 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). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md).
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : 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
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `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) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -105,6 +111,7 @@ Linear in the size of the input.
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0. - 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. - 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 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" !!! warning "Deprecation"
+11 -4
View File
@@ -7,8 +7,8 @@ static basic_json from_ubjson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_ubjson(IteratorType first, IteratorType last, static basic_json from_ubjson(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_ubjson(IteratorType first, IteratorType last,
Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format. Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format.
1. Reads from a compatible input. 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). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md).
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : 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
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `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) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -104,6 +110,7 @@ Linear in the size of the input.
- Added in version 3.1.0. - Added in version 3.1.0.
- Added `allow_exceptions` parameter in version 3.2.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 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" !!! warning "Deprecation"
+13 -5
View File
@@ -10,8 +10,8 @@ static basic_json parse(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json parse(IteratorType first, IteratorType last, static basic_json parse(IteratorType first, SentinelType last,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -19,10 +19,11 @@ static basic_json parse(IteratorType first, IteratorType last,
``` ```
1. Deserialize from a compatible input. 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 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 ## Template parameters
@@ -43,6 +44,12 @@ static basic_json parse(IteratorType first, IteratorType last,
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator` - a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
- a pair of pointers such as `ptr` and `ptr + len` - 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::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -67,7 +74,7 @@ static basic_json parse(IteratorType first, IteratorType last,
: iterator to the start of a character range : iterator to the start of a character range
`last` (in) `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 ## Return value
@@ -238,6 +245,7 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - 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. - 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 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" !!! warning "Deprecation"
+13 -5
View File
@@ -11,8 +11,8 @@ static bool sax_parse(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<class IteratorType, class SAX> template<class IteratorType, class SAX, class SentinelType = IteratorType>
static bool sax_parse(IteratorType first, IteratorType last, static bool sax_parse(IteratorType first, SentinelType last,
SAX* sax, SAX* sax,
input_format_t format = input_format_t::json, input_format_t format = input_format_t::json,
const bool strict = true, const bool strict = true,
@@ -23,10 +23,11 @@ static bool sax_parse(IteratorType first, IteratorType last,
Read from input and generate SAX events Read from input and generate SAX events
1. Read from a compatible input. 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 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). The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
@@ -46,6 +47,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type : 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) 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), for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
`SAX` `SAX`
: a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md) : a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md)
@@ -76,7 +83,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
: iterator to the start of a character range : iterator to the start of a character range
`last` (in) `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 ## Return value
@@ -128,6 +135,7 @@ A UTF-8 byte order mark is silently ignored.
- Ignoring comments via `ignore_comments` added in version 3.9.0. - Ignoring comments via `ignore_comments` added in version 3.9.0.
- Added `ignore_trailing_commas` in version 3.13.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 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" !!! warning "Deprecation"
@@ -155,7 +155,9 @@ class input_stream_adapter
// General-purpose iterator-based adapter. It might not be as fast as // General-purpose iterator-based adapter. It might not be as fast as
// theoretically possible for some containers, but it is extremely versatile. // 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 class iterator_input_adapter
{ {
public: public:
@@ -169,9 +171,10 @@ class iterator_input_adapter
// in wide_string_input_adapter, which does not expose this). // in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek = static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value 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; && 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)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
@@ -216,19 +219,30 @@ class iterator_input_adapter
private: private:
// whether IteratorType refers to a contiguous range and therefore supports // 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 // 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).
// Computing the available element count needs either same-type iterators
// (plain std::distance) or, in C++20, a sized sentinel (std::ranges::distance),
// e.g. std::counted_iterator paired with std::default_sentinel_t.
static constexpr bool iterator_is_contiguous = static constexpr bool iterator_is_contiguous =
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> || (std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>)
&& (std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
#else
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
#endif #endif
std::is_pointer<IteratorType>::value;
// contiguous fast path: bulk copy the remaining range with std::memcpy // contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T> template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/) std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{ {
const std::size_t wanted = count * sizeof(T); const std::size_t wanted = count * sizeof(T);
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// std::ranges::distance also supports sized sentinels of a different
// type (e.g. std::counted_iterator + std::default_sentinel_t)
const std::size_t available = static_cast<std::size_t>(std::ranges::distance(current, end)) * sizeof(char_type);
#else
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type); const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
#endif
const std::size_t copied = (std::min)(wanted, available); const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0)) if (JSON_HEDLEY_LIKELY(copied != 0))
{ {
@@ -267,7 +281,7 @@ class iterator_input_adapter
IteratorType begin; IteratorType begin;
IteratorType current; IteratorType current;
IteratorType end; SentinelType end;
template<typename BaseInputAdapter, size_t T> template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper; friend struct wide_string_input_helper;
@@ -453,19 +467,54 @@ class wide_string_input_adapter
std::size_t utf8_bytes_filled = 0; 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 struct iterator_input_adapter_factory
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; 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)); 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> template<typename T>
struct is_iterator_of_multibyte struct is_iterator_of_multibyte
{ {
@@ -476,25 +525,31 @@ struct is_iterator_of_multibyte
}; };
}; };
template<typename IteratorType> template<typename IteratorType, typename SentinelType>
struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; 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>; 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))); return adapter_type(base_adapter_type(std::move(first), std::move(last)));
} }
}; };
// General purpose iterator-based input // General purpose iterator-based input (iterator+sentinel pair; SentinelType
template<typename IteratorType> // defaults to IteratorType for the common same-type case, but may differ for
typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last) // 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); return factory_type::create(first, last);
} }
+32 -24
View File
@@ -4083,12 +4083,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; 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/ /// @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 JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(IteratorType first, static basic_json parse(IteratorType first,
IteratorType last, SentinelType last,
parser_callback_t cb = nullptr, parser_callback_t cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -4122,10 +4123,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); 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/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType,
static bool accept(IteratorType first, IteratorType last, 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_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
@@ -4157,11 +4159,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); : 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/ /// @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) 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, input_format_t format = input_format_t::json,
const bool strict = true, const bool strict = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -4461,11 +4464,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
@@ -4518,11 +4522,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4572,11 +4577,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4626,11 +4632,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4656,11 +4663,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
+104 -41
View File
@@ -6992,7 +6992,9 @@ class input_stream_adapter
// General-purpose iterator-based adapter. It might not be as fast as // General-purpose iterator-based adapter. It might not be as fast as
// theoretically possible for some containers, but it is extremely versatile. // 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 class iterator_input_adapter
{ {
public: public:
@@ -7006,9 +7008,10 @@ class iterator_input_adapter
// in wide_string_input_adapter, which does not expose this). // in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek = static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value 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; && 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)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
@@ -7053,19 +7056,30 @@ class iterator_input_adapter
private: private:
// whether IteratorType refers to a contiguous range and therefore supports // 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 // 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).
// Computing the available element count needs either same-type iterators
// (plain std::distance) or, in C++20, a sized sentinel (std::ranges::distance),
// e.g. std::counted_iterator paired with std::default_sentinel_t.
static constexpr bool iterator_is_contiguous = static constexpr bool iterator_is_contiguous =
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> || (std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>)
&& (std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
#else
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
#endif #endif
std::is_pointer<IteratorType>::value;
// contiguous fast path: bulk copy the remaining range with std::memcpy // contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T> template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/) std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{ {
const std::size_t wanted = count * sizeof(T); const std::size_t wanted = count * sizeof(T);
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// std::ranges::distance also supports sized sentinels of a different
// type (e.g. std::counted_iterator + std::default_sentinel_t)
const std::size_t available = static_cast<std::size_t>(std::ranges::distance(current, end)) * sizeof(char_type);
#else
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type); const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
#endif
const std::size_t copied = (std::min)(wanted, available); const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0)) if (JSON_HEDLEY_LIKELY(copied != 0))
{ {
@@ -7104,7 +7118,7 @@ class iterator_input_adapter
IteratorType begin; IteratorType begin;
IteratorType current; IteratorType current;
IteratorType end; SentinelType end;
template<typename BaseInputAdapter, size_t T> template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper; friend struct wide_string_input_helper;
@@ -7290,19 +7304,54 @@ class wide_string_input_adapter
std::size_t utf8_bytes_filled = 0; 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 struct iterator_input_adapter_factory
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; 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)); 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> template<typename T>
struct is_iterator_of_multibyte struct is_iterator_of_multibyte
{ {
@@ -7313,25 +7362,31 @@ struct is_iterator_of_multibyte
}; };
}; };
template<typename IteratorType> template<typename IteratorType, typename SentinelType>
struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; 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>; 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))); return adapter_type(base_adapter_type(std::move(first), std::move(last)));
} }
}; };
// General purpose iterator-based input // General purpose iterator-based input (iterator+sentinel pair; SentinelType
template<typename IteratorType> // defaults to IteratorType for the common same-type case, but may differ for
typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last) // 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); return factory_type::create(first, last);
} }
@@ -24934,12 +24989,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; 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/ /// @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 JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(IteratorType first, static basic_json parse(IteratorType first,
IteratorType last, SentinelType last,
parser_callback_t cb = nullptr, parser_callback_t cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -24973,10 +25029,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); 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/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType> template<typename IteratorType, typename SentinelType = IteratorType,
static bool accept(IteratorType first, IteratorType last, 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_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
@@ -25008,11 +25065,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); : 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/ /// @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) 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, input_format_t format = input_format_t::json,
const bool strict = true, const bool strict = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -25312,11 +25370,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
@@ -25369,11 +25428,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25423,11 +25483,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25477,11 +25538,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25507,11 +25569,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); 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/ /// @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 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 strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
+13
View File
@@ -30,4 +30,17 @@ inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
return byte_vector; 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 } // namespace utils
+9
View File
@@ -3699,6 +3699,15 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
} }
} }
TEST_CASE("Parse BJData directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bjdata";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_bjdata(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("all BJData first bytes") TEST_CASE("all BJData first bytes")
{ {
+13
View File
@@ -1208,6 +1208,19 @@ TEST_CASE("BSON numerical data")
} }
} }
TEST_CASE("Parse BSON directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json.org/1.json";
std::ifstream f_json(filename);
const json expected = json::parse(f_json);
std::ifstream file(filename + ".bson", std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_bson(first, utils::istreambuf_sentinel{});
CHECK(parsed == expected);
}
TEST_CASE("BSON roundtrips" * doctest::skip()) TEST_CASE("BSON roundtrips" * doctest::skip())
{ {
SECTION("reference files") SECTION("reference files")
+9
View File
@@ -1921,6 +1921,15 @@ TEST_CASE("single CBOR roundtrip")
} }
} }
TEST_CASE("Parse CBOR directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.cbor";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_cbor(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("CBOR regressions") TEST_CASE("CBOR regressions")
{ {
+9
View File
@@ -1641,6 +1641,15 @@ TEST_CASE("single MessagePack roundtrip")
} }
} }
TEST_CASE("Parse MessagePack directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.msgpack";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<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()) TEST_CASE("MessagePack roundtrips" * doctest::skip())
{ {
SECTION("input from msgpack-python") SECTION("input from msgpack-python")
+9
View File
@@ -2393,6 +2393,15 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
} }
} }
TEST_CASE("Parse UBJSON directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.ubjson";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_ubjson(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("all UBJSON first bytes") TEST_CASE("all UBJSON first bytes")
{ {
+61
View File
@@ -6,6 +6,13 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
// When using macros that are only defined for particular versions of the standard
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -13,6 +20,10 @@ using nlohmann::json;
#include <list> #include <list>
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
#include <iterator>
#endif
namespace namespace
{ {
TEST_CASE("Use arbitrary stdlib container") TEST_CASE("Use arbitrary stdlib container")
@@ -168,4 +179,54 @@ TEST_CASE("Custom iterator")
CHECK(as_json.at(3) == 4); 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);
}
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
{
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<std::string::const_iterator>>(json_str.size());
std::counted_iterator first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
std::counted_iterator first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
#endif
} // namespace } // namespace