Compare commits

..
Author SHA1 Message Date
Niels Lohmann bad2b22115 Merge remote-tracking branch 'origin/develop' into claude/todo-190-plan-e350df
# Conflicts:
#	include/nlohmann/detail/conversions/to_json.hpp
#	single_include/nlohmann/json.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 11:15:28 +02:00
Niels Lohmann a2be332fef Fix CI: missing include and const-correctness
- Add #include <vector> to type_traits.hpp for the new
  is_compatible_binary_type trait's std::vector<std::uint8_t> reference
  (caught by cpplint's include-what-you-use check)
- Mark test-local json variables const where never reassigned
  (caught by clang-tidy's misc-const-correctness check)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 22:31:03 +02:00
Niels Lohmann c37d96115d Fix discussion #4209: custom BinaryType direct assignment and extraction
When a custom BinaryType is configured (other than the default std::vector<uint8_t>),
users can now:
1. Assign values of that type directly to create binary values (not arrays)
2. Extract binary values back to that type with get<>()
3. Extract arrays to that type (for backward compatibility)

Implementation:
- Add is_compatible_binary_type trait to centralize SFINAE condition
- Update to_json to accept custom BinaryType values directly
- Update from_json to handle both binary and array inputs for custom BinaryType
- Add #include <vector> with IWYU comment to from_json.hpp
- Add comprehensive tests for assignment and array extraction
- Update binary_t documentation with example

This is purely additive and invisible to the default nlohmann::json alias, which
continues to treat std::vector<uint8_t> as arrays.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 19:46:07 +02:00
29 changed files with 194 additions and 722 deletions
+3 -3
View File
@@ -38,14 +38,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
languages: c-cpp languages: c-cpp
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
+1 -1
View File
@@ -43,6 +43,6 @@ jobs:
output: 'flawfinder_results.sarif' output: 'flawfinder_results.sarif'
- name: Upload analysis results to GitHub Security tab - name: Upload analysis results to GitHub Security tab
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+1 -1
View File
@@ -76,6 +76,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
# Upload SARIF file generated in previous step # Upload SARIF file generated in previous step
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: semgrep.sarif sarif_file: semgrep.sarif
if: always() if: always()
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
with: with:
egress-policy: audit egress-policy: audit
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
stale-issue-label: 'state: stale' stale-issue-label: 'state: stale'
stale-pr-label: 'state: stale' stale-pr-label: 'state: stale'
+5 -13
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, typename SentinelType = IteratorType> template<typename IteratorType>
static bool accept(IteratorType first, SentinelType last, static bool accept(IteratorType first, IteratorType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
``` ```
@@ -17,11 +17,10 @@ static bool accept(IteratorType first, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Reads from a pair of character iterators
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. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
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.
@@ -45,12 +44,6 @@ 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)
@@ -68,7 +61,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the character range
## Return value ## Return value
@@ -119,7 +112,6 @@ 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"
+4 -11
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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_bjdata(IteratorType first, SentinelType last, static basic_json from_bjdata(IteratorType first, IteratorType 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, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
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,12 +35,6 @@ 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)
@@ -50,7 +44,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`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)
@@ -109,4 +103,3 @@ 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.
+4 -11
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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_bson(IteratorType first, SentinelType last, static basic_json from_bson(IteratorType first, IteratorType 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, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
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,12 +35,6 @@ 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)
@@ -50,7 +44,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`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)
@@ -109,7 +103,6 @@ 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"
+4 -11
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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_cbor(IteratorType first, SentinelType last, static basic_json from_cbor(IteratorType first, IteratorType 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, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
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,12 +38,6 @@ 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)
@@ -53,7 +47,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`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)
@@ -119,7 +113,6 @@ 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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_msgpack(IteratorType first, SentinelType last, static basic_json from_msgpack(IteratorType first, IteratorType 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, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
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,12 +35,6 @@ 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)
@@ -50,7 +44,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`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)
@@ -111,7 +105,6 @@ 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"
+4 -11
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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_ubjson(IteratorType first, SentinelType last, static basic_json from_ubjson(IteratorType first, IteratorType 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, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
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,12 +35,6 @@ 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)
@@ -50,7 +44,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`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)
@@ -110,7 +104,6 @@ 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"
+5 -13
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, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json parse(IteratorType first, SentinelType last, static basic_json parse(IteratorType first, IteratorType 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,11 +19,10 @@ static basic_json parse(IteratorType first, SentinelType last,
``` ```
1. Deserialize from a compatible input. 1. Deserialize from a compatible input.
2. Deserialize from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Deserialize from a pair of character iterators
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. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
the iterator type with `operator!=`.
## Template parameters ## Template parameters
@@ -44,12 +43,6 @@ static basic_json parse(IteratorType first, SentinelType 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)
@@ -74,7 +67,7 @@ static basic_json parse(IteratorType first, SentinelType 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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of a character range
## Return value ## Return value
@@ -245,7 +238,6 @@ 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"
+5 -13
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, class SentinelType = IteratorType> template<class IteratorType, class SAX>
static bool sax_parse(IteratorType first, SentinelType last, static bool sax_parse(IteratorType first, IteratorType 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,11 +23,10 @@ static bool sax_parse(IteratorType first, SentinelType 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, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Read from a pair of character iterators
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. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
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).
@@ -47,12 +46,6 @@ 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)
@@ -83,7 +76,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, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of a character range
## Return value ## Return value
@@ -135,7 +128,6 @@ 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"
@@ -38,8 +38,7 @@ When the macro is not defined, the library will define it to its default value.
Diagnostic messages can also be controlled with the CMake option Diagnostic messages can also be controlled with the CMake option
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default) [`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
which defines `JSON_DIAGNOSTICS` accordingly. Note this only applies when building the which defines `JSON_DIAGNOSTICS` accordingly.
library from source — see the pre-installed-package caveat on that page.
## Examples ## Examples
-36
View File
@@ -47,28 +47,6 @@ json j = {{"one", 1}, {"two", 2}};
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}} auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
``` ```
`#!cpp std::pair` and `#!cpp std::tuple` are also supported, converting positionally to and from a JSON array:
```cpp
json j = {1.0, "hello", 42};
auto t = j.get<std::tuple<double, std::string, int>>(); // {1.0, "hello", 42}
```
!!! info "Extracting references into a tuple"
A tuple type may also hold references (e.g. `#!cpp std::tuple<double&, std::string&>`) to avoid copying: `get`
then returns a tuple of references pointing directly at the elements stored inside the `basic_json` array,
rather than a tuple of copies:
```cpp
json j = {1.0, "hello"};
auto refs = j.get<std::tuple<double&, std::string&>>();
std::get<1>(refs) = "world"; // modifies j[1] in place
```
A referenced type must be one the library actually stores (or an arithmetic type it can convert to/from);
otherwise this is a compile error.
## Implicit conversions ## Implicit conversions
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted: By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
@@ -158,20 +136,6 @@ std::vector<int> numbers = {1, 2, 3};
json j = numbers; // [1,2,3] json j = numbers; // [1,2,3]
``` ```
!!! info "Constructing from a C++20 range view"
A `json` array can also be constructed directly from a C++20 range view (`std::ranges::view`), such as the result
of `std::views::filter` or `std::views::transform` -- no intermediate container is needed:
```cpp
std::vector<int> nums{1, 2, 37, 42, 21};
auto filtered = nums | std::views::filter([](int i) { return i > 10; });
json j(filtered); // [37,42,21]
```
This requires [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) to be enabled and is unavailable on MinGW due
to incomplete C++20 ranges support there.
## Your own types ## Your own types
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
-25
View File
@@ -135,31 +135,6 @@ Enable CI build targets. The exact targets are used during the several CI steps
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default. Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
!!! warning "Does not apply to a pre-installed package"
This option only takes effect when building nlohmann/json from source as part of your own
CMake project (e.g. via [`FetchContent`](#fetchcontent) or [`add_subdirectory`](#external)).
It has **no effect** on a package that was already built and installed elsewhere (Homebrew,
vcpkg, a system package, etc.) — the resulting compile definition is baked into the exported
`nlohmann_jsonTargets.cmake` at install time, and `set(JSON_Diagnostics ON)` before
`find_package()` does not change it (verified against the Homebrew-installed package: the
exported target still carries a fixed `$<$<BOOL:OFF>:JSON_DIAGNOSTICS=1>`, regardless of any
variable set in the consuming project).
To enable extended diagnostics for a pre-installed package, override the imported target's
property directly after `find_package()`:
```cmake
find_package(nlohmann_json REQUIRED)
set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "JSON_DIAGNOSTICS=1")
```
This only works cleanly when your project is the sole consumer of that imported target. If
nlohmann_json is pulled in from more than one place in your dependency graph with different
`JSON_DIAGNOSTICS` values, you may see a `"JSON_DIAGNOSTICS" redefined` compiler error, since
conflicting `-D` flags can end up on the same compile command line.
### `JSON_Diagnostic_Positions` ### `JSON_Diagnostic_Positions`
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default. Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default.
+26 -49
View File
@@ -1846,47 +1846,6 @@ class binary_reader
return get_ubjson_value(get_char ? get_ignore_noop() : current); return get_ubjson_value(get_char ? get_ignore_noop() : current);
} }
/*!
@brief reject a negative UBJSON/BJData string length
String and key lengths are written with signed integer markers (i, I, l,
L). A negative value is malformed; without this check get_string() would
silently treat it as an empty string and leave the following bytes to be
misread as the next value. This mirrors the non-negative check the
optimized-container count path already performs in get_ubjson_size_value.
@param[in] len the string length read from the input
@return whether the length is valid (non-negative)
*/
template<typename NumberType>
bool check_ubjson_string_length(const NumberType len)
{
if (JSON_HEDLEY_UNLIKELY(len < 0))
{
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,
exception_message(input_format, "string length must not be negative", "string"), nullptr));
}
return true;
}
/*!
@brief create the error message for a missing/invalid length-type marker
Used both when reading a UBJSON/BJData string and when reading an optimized
container size. The accepted markers depend on the input format, and the
size variant appends " after '#'" via @a infix.
@param[in] last_token the offending byte as returned by get_token_string()
@param[in] infix extra context inserted after the marker list
@return the formatted error message
*/
std::string unexpected_length_type_message(const std::string& last_token, const char* infix) const
{
return concat("expected length type specification (",
input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L",
")", infix, "; last byte: 0x", last_token);
}
/*! /*!
@brief reads a UBJSON string @brief reads a UBJSON string
@@ -1924,25 +1883,25 @@ class binary_reader
case 'i': case 'i':
{ {
std::int8_t len{}; std::int8_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'I': case 'I':
{ {
std::int16_t len{}; std::int16_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'l': case 'l':
{ {
std::int32_t len{}; std::int32_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'L': case 'L':
{ {
std::int64_t len{}; std::int64_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'u': case 'u':
@@ -1979,8 +1938,17 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, std::string message;
exception_message(input_format, unexpected_length_type_message(last_token, ""), "string"), nullptr));
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr));
} }
/*! /*!
@@ -2259,8 +2227,17 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, std::string message;
exception_message(input_format, unexpected_length_type_message(last_token, " after '#'"), "size"), nullptr));
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr));
} }
/*! /*!
@@ -155,9 +155,7 @@ 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.
// SentinelType defaults to IteratorType for backward compatibility, but may template<typename IteratorType>
// 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:
@@ -171,10 +169,9 @@ 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, SentinelType last) iterator_input_adapter(IteratorType first, IteratorType last)
: begin(first), current(std::move(first)), end(std::move(last)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
@@ -219,30 +216,19 @@ 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::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>) std::contiguous_iterator<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))
{ {
@@ -281,7 +267,7 @@ class iterator_input_adapter
IteratorType begin; IteratorType begin;
IteratorType current; IteratorType current;
SentinelType end; IteratorType 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;
@@ -395,30 +381,17 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
} }
else else
{ {
// A supplementary code point is a high surrogate (0xD800..0xDBFF) if (JSON_HEDLEY_UNLIKELY(!input.empty()))
// followed by a low surrogate (0xDC00..0xDFFF). A lone low
// surrogate, a high surrogate at the end of the input, or a high
// surrogate followed by any other unit is malformed UTF-16. In
// that case the offending unit is passed through unchanged so the
// UTF-8 decoder rejects it, matching how \uXXXX surrogate escapes
// are handled in the lexer.
bool valid_pair = false;
if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty()))
{ {
const auto wc2 = static_cast<unsigned int>(input.get_character()); const auto wc2 = static_cast<unsigned int>(input.get_character());
if (0xDC00 <= wc2 && wc2 <= 0xDFFF) const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
{ utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes_filled = 4;
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
valid_pair = true;
}
} }
else
if (!valid_pair)
{ {
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1; utf8_bytes_filled = 1;
@@ -480,54 +453,19 @@ class wide_string_input_adapter
std::size_t utf8_bytes_filled = 0; std::size_t utf8_bytes_filled = 0;
}; };
template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void> template<typename 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, sentinel_type>; using adapter_type = iterator_input_adapter<iterator_type>;
static adapter_type create(IteratorType first, SentinelType last) static adapter_type create(IteratorType first, IteratorType 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
{ {
@@ -538,31 +476,25 @@ struct is_iterator_of_multibyte
}; };
}; };
template<typename IteratorType, typename SentinelType> template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> struct iterator_input_adapter_factory<IteratorType, 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, sentinel_type>; using base_adapter_type = iterator_input_adapter<iterator_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, SentinelType last) static adapter_type create(IteratorType first, IteratorType 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 (iterator+sentinel pair; SentinelType // General purpose iterator-based input
// defaults to IteratorType for the common same-type case, but may differ for template<typename IteratorType>
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
// 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, SentinelType>; using factory_type = iterator_input_adapter_factory<IteratorType>;
return factory_type::create(first, last); return factory_type::create(first, last);
} }
+24 -32
View File
@@ -4083,13 +4083,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @brief deserialize from a pair of character iterators
/// @sa https://json.nlohmann.me/api/basic_json/parse/ /// @sa https://json.nlohmann.me/api/basic_json/parse/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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,
SentinelType last, IteratorType 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,
@@ -4123,11 +4122,10 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0> 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)
{ {
@@ -4159,12 +4157,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief generate SAX events
/// @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, class SentinelType = IteratorType, template<class IteratorType, class SAX>
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, SentinelType last, SAX* sax, static bool sax_parse(IteratorType first, IteratorType 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,
@@ -4464,12 +4461,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in CBOR format
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_cbor(IteratorType first, IteratorType 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)
@@ -4522,12 +4518,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in MessagePack format
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_msgpack(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4577,12 +4572,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_ubjson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4632,12 +4626,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BJData format
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4663,12 +4656,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/ /// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_bson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
+75 -174
View File
@@ -7142,9 +7142,7 @@ 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.
// SentinelType defaults to IteratorType for backward compatibility, but may template<typename IteratorType>
// 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:
@@ -7158,10 +7156,9 @@ 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, SentinelType last) iterator_input_adapter(IteratorType first, IteratorType last)
: begin(first), current(std::move(first)), end(std::move(last)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
@@ -7206,30 +7203,19 @@ 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::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>) std::contiguous_iterator<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))
{ {
@@ -7268,7 +7254,7 @@ class iterator_input_adapter
IteratorType begin; IteratorType begin;
IteratorType current; IteratorType current;
SentinelType end; IteratorType 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;
@@ -7382,30 +7368,17 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
} }
else else
{ {
// A supplementary code point is a high surrogate (0xD800..0xDBFF) if (JSON_HEDLEY_UNLIKELY(!input.empty()))
// followed by a low surrogate (0xDC00..0xDFFF). A lone low
// surrogate, a high surrogate at the end of the input, or a high
// surrogate followed by any other unit is malformed UTF-16. In
// that case the offending unit is passed through unchanged so the
// UTF-8 decoder rejects it, matching how \uXXXX surrogate escapes
// are handled in the lexer.
bool valid_pair = false;
if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty()))
{ {
const auto wc2 = static_cast<unsigned int>(input.get_character()); const auto wc2 = static_cast<unsigned int>(input.get_character());
if (0xDC00 <= wc2 && wc2 <= 0xDFFF) const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
{ utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes_filled = 4;
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
valid_pair = true;
}
} }
else
if (!valid_pair)
{ {
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1; utf8_bytes_filled = 1;
@@ -7467,54 +7440,19 @@ class wide_string_input_adapter
std::size_t utf8_bytes_filled = 0; std::size_t utf8_bytes_filled = 0;
}; };
template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void> template<typename 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, sentinel_type>; using adapter_type = iterator_input_adapter<iterator_type>;
static adapter_type create(IteratorType first, SentinelType last) static adapter_type create(IteratorType first, IteratorType 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
{ {
@@ -7525,31 +7463,25 @@ struct is_iterator_of_multibyte
}; };
}; };
template<typename IteratorType, typename SentinelType> template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> struct iterator_input_adapter_factory<IteratorType, 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, sentinel_type>; using base_adapter_type = iterator_input_adapter<iterator_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, SentinelType last) static adapter_type create(IteratorType first, IteratorType 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 (iterator+sentinel pair; SentinelType // General purpose iterator-based input
// defaults to IteratorType for the common same-type case, but may differ for template<typename IteratorType>
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
// 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, SentinelType>; using factory_type = iterator_input_adapter_factory<IteratorType>;
return factory_type::create(first, last); return factory_type::create(first, last);
} }
@@ -12395,47 +12327,6 @@ class binary_reader
return get_ubjson_value(get_char ? get_ignore_noop() : current); return get_ubjson_value(get_char ? get_ignore_noop() : current);
} }
/*!
@brief reject a negative UBJSON/BJData string length
String and key lengths are written with signed integer markers (i, I, l,
L). A negative value is malformed; without this check get_string() would
silently treat it as an empty string and leave the following bytes to be
misread as the next value. This mirrors the non-negative check the
optimized-container count path already performs in get_ubjson_size_value.
@param[in] len the string length read from the input
@return whether the length is valid (non-negative)
*/
template<typename NumberType>
bool check_ubjson_string_length(const NumberType len)
{
if (JSON_HEDLEY_UNLIKELY(len < 0))
{
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,
exception_message(input_format, "string length must not be negative", "string"), nullptr));
}
return true;
}
/*!
@brief create the error message for a missing/invalid length-type marker
Used both when reading a UBJSON/BJData string and when reading an optimized
container size. The accepted markers depend on the input format, and the
size variant appends " after '#'" via @a infix.
@param[in] last_token the offending byte as returned by get_token_string()
@param[in] infix extra context inserted after the marker list
@return the formatted error message
*/
std::string unexpected_length_type_message(const std::string& last_token, const char* infix) const
{
return concat("expected length type specification (",
input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L",
")", infix, "; last byte: 0x", last_token);
}
/*! /*!
@brief reads a UBJSON string @brief reads a UBJSON string
@@ -12473,25 +12364,25 @@ class binary_reader
case 'i': case 'i':
{ {
std::int8_t len{}; std::int8_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'I': case 'I':
{ {
std::int16_t len{}; std::int16_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'l': case 'l':
{ {
std::int32_t len{}; std::int32_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'L': case 'L':
{ {
std::int64_t len{}; std::int64_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result);
} }
case 'u': case 'u':
@@ -12528,8 +12419,17 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, std::string message;
exception_message(input_format, unexpected_length_type_message(last_token, ""), "string"), nullptr));
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr));
} }
/*! /*!
@@ -12808,8 +12708,17 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, std::string message;
exception_message(input_format, unexpected_length_type_message(last_token, " after '#'"), "size"), nullptr));
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr));
} }
/*! /*!
@@ -25175,13 +25084,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @brief deserialize from a pair of character iterators
/// @sa https://json.nlohmann.me/api/basic_json/parse/ /// @sa https://json.nlohmann.me/api/basic_json/parse/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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,
SentinelType last, IteratorType 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,
@@ -25215,11 +25123,10 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0> 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)
{ {
@@ -25251,12 +25158,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief generate SAX events
/// @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, class SentinelType = IteratorType, template<class IteratorType, class SAX>
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, SentinelType last, SAX* sax, static bool sax_parse(IteratorType first, IteratorType 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,
@@ -25556,12 +25462,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in CBOR format
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_cbor(IteratorType first, IteratorType 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)
@@ -25614,12 +25519,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in MessagePack format
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_msgpack(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25669,12 +25573,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_ubjson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25724,12 +25627,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BJData format
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -25755,12 +25657,11 @@ 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 (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/ /// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename 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, SentinelType last, static basic_json from_bson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
-13
View File
@@ -30,17 +30,4 @@ 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
-22
View File
@@ -2721,19 +2721,6 @@ TEST_CASE("BJData")
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
} }
SECTION("negative length")
{
json _;
std::vector<uint8_t> const vi = {'S', 'i', 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
CHECK(json::from_bjdata(vi, true, false).is_discarded());
std::vector<uint8_t> const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
CHECK(json::from_bjdata(vl, true, false).is_discarded());
}
SECTION("parse bjdata markers in ubjson") SECTION("parse bjdata markers in ubjson")
{ {
// create a single-character string for all number types // create a single-character string for all number types
@@ -3712,15 +3699,6 @@ 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,19 +1208,6 @@ 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,15 +1921,6 @@ 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")
{ {
-15
View File
@@ -1782,21 +1782,6 @@ TEST_CASE("std::optional")
"[json.exception.type_error.302] type must be string, but is null", json::type_error&); "[json.exception.type_error.302] type must be string, but is null", json::type_error&);
CHECK_THROWS_WITH_AS(std::optional<int>(j_null), CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
"[json.exception.type_error.302] type must be number, but is null", json::type_error&); "[json.exception.type_error.302] type must be number, but is null", json::type_error&);
// Assignment goes through the same overload resolution as direct
// construction, so it throws for the same reason. This relies on
// basic_json's implicit conversion operator, so it only applies
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
#if JSON_USE_IMPLICIT_CONVERSIONS
std::optional<std::string> opt_assign;
CHECK_THROWS_WITH_AS(opt_assign = j_null,
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
#endif
// get_to() is the correct way to obtain std::nullopt from a JSON null.
std::optional<std::string> opt_get_to = "placeholder";
j_null.get_to(opt_get_to);
CHECK(opt_get_to == std::nullopt);
} }
SECTION("string") SECTION("string")
-9
View File
@@ -1641,15 +1641,6 @@ 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")
-34
View File
@@ -1862,31 +1862,6 @@ TEST_CASE("UBJSON")
json _; json _;
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&); CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&);
} }
SECTION("negative length")
{
json _;
std::vector<uint8_t> const vi = {'S', 'i', 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
CHECK(json::from_ubjson(vi, true, false).is_discarded());
std::vector<uint8_t> const vI = {'S', 'I', 0xFF, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vI), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
CHECK(json::from_ubjson(vI, true, false).is_discarded());
std::vector<uint8_t> const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
CHECK(json::from_ubjson(vl, true, false).is_discarded());
std::vector<uint8_t> const vL = {'S', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vL), "[json.exception.parse_error.113] parse error at byte 10: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
CHECK(json::from_ubjson(vL, true, false).is_discarded());
// a length of zero remains valid and yields an empty string
std::vector<uint8_t> const v0 = {'S', 'i', 0};
CHECK(json::from_ubjson(v0) == json(""));
}
} }
SECTION("array") SECTION("array")
@@ -2418,15 +2393,6 @@ 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")
{ {
-62
View File
@@ -6,13 +6,6 @@
// 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>
@@ -20,10 +13,6 @@ 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")
@@ -179,55 +168,4 @@ 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")
{
using iterator_type = std::string::const_iterator;
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
#endif
} // namespace } // namespace
+1 -33
View File
@@ -53,27 +53,6 @@ TEST_CASE("wide strings")
std::wstring const w = L"\"\xDBFF"; std::wstring const w = L"\"\xDBFF";
json _; json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
} }
} }
@@ -89,22 +68,11 @@ TEST_CASE("wide strings")
SECTION("invalid std::u16string") SECTION("invalid std::u16string")
{ {
if (u16string_is_utf16()) if (wstring_is_utf16())
{ {
std::u16string const w = u"\"\xDBFF"; std::u16string const w = u"\"\xDBFF";
json _; json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
} }
} }