Compare commits

..

2 Commits

Author SHA1 Message Date
Niels Lohmann cb7f1ae838 Avoid strlen() in test container to fix Codacy CWE-126 flag
Suppressing the strlen()-based CWE-126 warning with NOLINT/nosec
comments only silenced clang-tidy and the standalone Flawfinder
Action; Codacy's own analysis (which also flags this pattern and
doesn't honor those suppression comments) still reported it as a new
issue, plus flagged the near-duplicate begin/end pair as cloned code.

Store the buffer's size explicitly in MyContainerNonConstADL instead
of computing it via strlen() in end(), which removes the flagged
pattern outright and also de-duplicates the struct from the existing
MyContainer's char*-based begin/end pair.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-09 21:26:24 +02:00
Niels Lohmann fc7fde6910 Fix container input_adapter SFINAE for lvalue-only ADL begin/end (#111)
The container overload of json::parse(c) / accept(c) / sax_parse(c, ...)
silently dropped from overload resolution for user types whose ADL
begin(T&) / end(T&) accepted only non-const lvalue references
(a legitimate pattern matching std::begin semantics). This was because
the detection code used std::declval<ContainerType>() which synthesized
an rvalue, and the rvalue failed to bind to lvalue-only ADL functions.

Fix by making both the outer input_adapter(ContainerType&&) and the
factory's create(ContainerType&&) forwarding references, preserving the
caller's value category and constness via reference collapsing. This
ensures detection (std::declval) and actual use (std::forward) always
match without needing decay/remove_reference.

- Rewrite input_adapters.hpp container overload with forwarding refs
- Add regression tests for lvalue-only non-const ADL begin/end
- Add regression test for rvalue containers (no breakage)
- Update API docs (parse, accept, sax_parse, from_*) to clarify
  that begin/end must match std::begin/std::end semantics
- Add version history notes for 3.13.0
- Regenerate amalgamation

Second-order effect: binary_reader.hpp's internal call to
input_adapter(number_vector) now deduces iterator vs const_iterator
based on the lvalue; functionally harmless (iterator_input_adapter is
iterator-type-agnostic), verified via unit-ubjson/unit-bjdata tests.

Closes remaining limitation from #4354 / PR #5218 (todo 106).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-09 21:22:17 +02:00
26 changed files with 143 additions and 176 deletions
+3 -5
View File
@@ -5,11 +5,8 @@
# -Wno-extra-semi-stmt The library uses assert which triggers this warning. # -Wno-extra-semi-stmt The library uses assert which triggers this warning.
# -Wno-padded We do not care about padding warnings. # -Wno-padded We do not care about padding warnings.
# -Wno-covered-switch-default All switches list all cases and a default case. # -Wno-covered-switch-default All switches list all cases and a default case.
# -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code # -Wno-unsafe-buffer-usage Otherwise Doctest would not compile.
# (to_chars, serializer, lexer, binary reader/writer, input # -Wno-missing-noreturn We found no way to silence this warning otherwise, see PR #4871
# adapters, json_pointer) plus vendored Doctest itself (~208
# distinct sites measured 2026-07-08 on clang trunk) all use
# raw pointer arithmetic / libc string calls by necessity.
set(CLANG_CXXFLAGS set(CLANG_CXXFLAGS
-Werror -Werror
@@ -21,4 +18,5 @@ set(CLANG_CXXFLAGS
-Wno-padded -Wno-padded
-Wno-covered-switch-default -Wno-covered-switch-default
-Wno-unsafe-buffer-usage -Wno-unsafe-buffer-usage
-Wno-missing-noreturn
) )
+1 -1
View File
@@ -4,7 +4,7 @@
"archive": "JSON_for_Modern_C++.tgz", "archive": "JSON_for_Modern_C++.tgz",
"author": { "author": {
"name": "Niels Lohmann", "name": "Niels Lohmann",
"link": "https://nlohmann.me" "link": "https://twitter.com/nlohmann"
}, },
"aliases": ["nlohmann/json"] "aliases": ["nlohmann/json"]
} }
+3 -1
View File
@@ -35,7 +35,8 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null) - a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string` - a `std::string`
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type, for instance. : a compatible iterator type, for instance.
@@ -110,6 +111,7 @@ A UTF-8 byte order mark is silently ignored.
- Ignoring comments via `ignore_comments` added in version 3.9.0. - Ignoring comments via `ignore_comments` added in version 3.9.0.
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
@@ -101,3 +102,4 @@ Linear in the size of the input.
## Version history ## Version history
- 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.
+3 -1
View File
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
@@ -101,6 +102,7 @@ Linear in the size of the input.
## Version history ## Version history
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
+3 -1
View File
@@ -32,7 +32,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
@@ -111,6 +112,7 @@ Linear in the size of the input.
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0. - Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
@@ -103,6 +104,7 @@ Linear in the size of the input.
- Parameter `start_index` since version 2.1.1. - Parameter `start_index` since version 2.1.1.
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
@@ -102,6 +103,7 @@ Linear in the size of the input.
- Added in version 3.1.0. - Added in version 3.1.0.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
+1 -2
View File
@@ -63,8 +63,7 @@ behavior:
object will agree on the name-value mappings. object will agree on the name-value mappings.
- When the names within an object are not unique, it is unspecified which one of the values for a given key will be - When the names within an object are not unique, it is unspecified which one of the values for a given key will be
chosen. For instance, `#!json {"key": 2, "key": 1}` could be equal to either `#!json {"key": 1}` or chosen. For instance, `#!json {"key": 2, "key": 1}` could be equal to either `#!json {"key": 1}` or
`#!json {"key": 2}`. To reject duplicate keys instead of silently resolving them one way or another, see `#!json {"key": 2}`.
[this parsing recipe](../../features/parsing/parser_callbacks.md#recipe-rejecting-duplicate-object-keys).
- Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see
[`dump`](dump.md)) in this order. For instance, `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be stored [`dump`](dump.md)) in this order. For instance, `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be stored
and serialized as `#!json {"a": 2, "b": 1}`. and serialized as `#!json {"a": 2, "b": 1}`.
+12 -11
View File
@@ -19,8 +19,10 @@ class basic_json {
}; };
``` ```
1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)` (until C++20) or `#!cpp !(*this == rhs)` (since C++20). 1. Compares two JSON values for inequality according to the following rules:
- This means the comparison is simply the logical negation of `operator==`, including for special values like `NaN` and `discarded`. - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either operand is `NaN` and
the other operand is either `NaN` or any other number.
- Otherwise, returns the result of `#!cpp !(lhs == rhs)` (until C++20) or `#!cpp !(*this == rhs)` (since C++20).
2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the scalar to a JSON 2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the scalar to a JSON
value and comparing both JSON values according to 1. value and comparing both JSON values according to 1.
@@ -52,12 +54,13 @@ Linear.
## Notes ## Notes
!!! note "Comparing `NaN` and `discarded`" !!! note "Comparing `NaN`"
Since `operator!=` is defined as `!(a == b)`, the behavior for special values follows that of `operator==`: `NaN` values are unordered within the domain of numbers.
The following comparisons all yield `#!cpp false`:
- For `NaN` values: `NaN == NaN` yields `#!cpp false`, so `NaN != NaN` yields `#!cpp true`. 1. Comparing a `NaN` with itself.
- For `discarded` values: `discarded == x` yields `#!cpp false` for any `x`, so `discarded != x` yields `#!cpp true`. 2. Comparing a `NaN` with another `NaN`.
3. Comparing a `NaN` and any other number.
## Examples ## Examples
@@ -91,7 +94,5 @@ Linear.
## Version history ## Version history
1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove 1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0.
special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. 2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0.
2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove
special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`.
+3 -1
View File
@@ -34,7 +34,8 @@ static basic_json parse(IteratorType first, IteratorType last,
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null) - a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string` - a `std::string`
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type, for instance. : a compatible iterator type, for instance.
@@ -236,6 +237,7 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
- Ignoring comments via `ignore_comments` added in version 3.9.0. - Ignoring comments via `ignore_comments` added in version 3.9.0.
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
+3 -2
View File
@@ -39,8 +39,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
- a `FILE` pointer - a `FILE` pointer
- a C-style array of characters - a C-style array of characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of - a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
iterators. (as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType` `IteratorType`
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type : a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
@@ -127,6 +127,7 @@ A UTF-8 byte order mark is silently ignored.
- Added in version 3.2.0. - Added in version 3.2.0.
- 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.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -22,8 +22,6 @@ When the macro is not defined, the library will define it to its default value.
- **libstdc++ < 11** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - **libstdc++ < 11** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440))
- **Clang < 16 with libstdc++** — disabled (incomplete ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - **Clang < 16 with libstdc++** — disabled (incomplete ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440))
- **libc++ < 160000** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - **libc++ < 160000** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440))
- **nvcc (CUDA) 12.0.x and 12.1.x** — disabled (the `enable_borrowed_range` variable-template syntax triggers a parse error
under these two toolkit versions; fixed in CUDA 12.2; [issue #3907](https://github.com/nlohmann/json/issues/3907))
If `JSON_HAS_RANGES` is `0` despite `__cpp_lib_ranges` being defined, one of the exclusions above likely applies to your toolchain. If `JSON_HAS_RANGES` is `0` despite `__cpp_lib_ranges` being defined, one of the exclusions above likely applies to your toolchain.
@@ -1,61 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <vector>
using json = nlohmann::json;
json parse_strict(const std::string& input)
{
// one key set per nesting depth, reused across sibling objects
std::vector<std::unordered_set<std::string>> keys;
auto reject_duplicate_keys = [&](int depth, json::parse_event_t event, json & parsed)
{
if (event == json::parse_event_t::object_start)
{
// keys of this object are reported at depth+1 (see the event table above)
const auto child_depth = static_cast<std::size_t>(depth) + 1;
if (keys.size() <= child_depth)
{
keys.resize(child_depth + 1);
}
keys[child_depth].clear();
return true;
}
if (event == json::parse_event_t::key)
{
auto& seen = keys[static_cast<std::size_t>(depth)];
const auto& key = parsed.get_ref<const std::string&>();
if (!seen.insert(key).second)
{
throw std::runtime_error("duplicate JSON object key: " + key);
}
return true;
}
return true;
};
return json::parse(input, reject_duplicate_keys);
}
int main()
{
// parsing succeeds when all keys are unique
json j = parse_strict(R"({"one": 1, "two": 2})");
std::cout << j << '\n';
// parsing throws when a key is repeated
try
{
parse_strict(R"({"one": 1, "one": 2})");
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
}
@@ -1,2 +0,0 @@
{"one":1,"two":2}
duplicate JSON object key: one
@@ -81,34 +81,3 @@ was called:
```json ```json
--8<-- "examples/parse__string__parser_callback_t.output" --8<-- "examples/parse__string__parser_callback_t.output"
``` ```
## Recipe: rejecting duplicate object keys
The JSON specification leaves the handling of objects with repeated keys up to the implementation. As described in
[`object_t`](../../api/basic_json/object_t.md#behavior), it is unspecified which value for a repeated key ends up in
the resulting `#!c json` value -- once parsing has produced that value, the duplicate is already gone, because object
storage maps each key to a single value. If duplicate keys should instead be treated as an error, a parser callback
can detect them while the object is still being read, before that ambiguity ever applies.
??? example
```cpp
--8<-- "examples/reject_duplicate_keys.cpp"
```
Output:
```json
--8<-- "examples/reject_duplicate_keys.output"
```
This approach has two limitations:
- The depth-indexed bookkeeping must account for the fact that `object_start` reports the depth of the *parent* of
the object, while the `key` events inside that object are reported one depth deeper (see the event table above);
it is easy to get this off by one for nested objects.
- The thrown exception cannot carry a `parse_error`-style byte offset, because position tracking only exists inside
the parser and lexer, not at the callback layer.
For strict validation with precise error positions, implementing a [SAX interface](sax_interface.md) instead gives
access to the parser's position information directly.
+1 -1
View File
@@ -131,7 +131,7 @@ std::map<
The choice of `object_t` influences the behavior of the JSON class. With the default type, objects have the following behavior: The choice of `object_t` influences the behavior of the JSON class. With the default type, objects have the following behavior:
- When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings.
- When the names within an object are not unique, it is unspecified which one of the values for a given key will be chosen. For instance, `#!json {"key": 2, "key": 1}` could be equal to either `#!json {"key": 1}` or `#!json {"key": 2}`. To reject duplicate keys instead of silently resolving them one way or another, see [this parsing recipe](../parsing/parser_callbacks.md#recipe-rejecting-duplicate-object-keys). - When the names within an object are not unique, it is unspecified which one of the values for a given key will be chosen. For instance, `#!json {"key": 2, "key": 1}` could be equal to either `#!json {"key": 1}` or `#!json {"key": 2}`.
- Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see `dump`) in this order. For instance, both `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be stored and serialized as `#!json {"a": 2, "b": 1}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see `dump`) in this order. For instance, both `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be stored and serialized as `#!json {"a": 2, "b": 1}`.
- When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be treated as equal. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be treated as equal.
@@ -430,7 +430,7 @@ class wide_string_input_adapter
// parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here
template<class T> template<class T>
JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1)
{ {
JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr));
} }
@@ -517,18 +517,19 @@ struct container_input_adapter_factory< ContainerType,
{ {
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))); using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
static adapter_type create(const ContainerType& container) static adapter_type create(ContainerType&& container)
{ {
return input_adapter(begin(container), end(container)); return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
} }
}; };
} // namespace container_input_adapter_factory_impl } // namespace container_input_adapter_factory_impl
template<typename ContainerType> template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container) auto input_adapter(ContainerType&& container)
-> typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type
{ {
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container); return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
} }
// specialization for std::string // specialization for std::string
+15
View File
@@ -3776,6 +3776,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *this == basic_json(rhs); return *this == basic_json(rhs);
} }
/// @brief comparison: not equal
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
bool operator!=(const_reference rhs) const noexcept
{
if (compares_unordered(rhs, true))
{
return false;
}
return !operator==(rhs);
}
/// @brief comparison: 3-way /// @brief comparison: 3-way
/// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
@@ -3881,6 +3892,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
friend bool operator!=(const_reference lhs, const_reference rhs) noexcept friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
{ {
if (compares_unordered(lhs, rhs, true))
{
return false;
}
return !(lhs == rhs); return !(lhs == rhs);
} }
+21 -5
View File
@@ -7267,7 +7267,7 @@ class wide_string_input_adapter
// parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here
template<class T> template<class T>
JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1)
{ {
JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr));
} }
@@ -7354,18 +7354,19 @@ struct container_input_adapter_factory< ContainerType,
{ {
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))); using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
static adapter_type create(const ContainerType& container) static adapter_type create(ContainerType&& container)
{ {
return input_adapter(begin(container), end(container)); return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
} }
}; };
} // namespace container_input_adapter_factory_impl } // namespace container_input_adapter_factory_impl
template<typename ContainerType> template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container) auto input_adapter(ContainerType&& container)
-> typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type
{ {
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container); return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
} }
// specialization for std::string // specialization for std::string
@@ -24627,6 +24628,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *this == basic_json(rhs); return *this == basic_json(rhs);
} }
/// @brief comparison: not equal
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
bool operator!=(const_reference rhs) const noexcept
{
if (compares_unordered(rhs, true))
{
return false;
}
return !operator==(rhs);
}
/// @brief comparison: 3-way /// @brief comparison: 3-way
/// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
@@ -24732,6 +24744,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
friend bool operator!=(const_reference lhs, const_reference rhs) noexcept friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
{ {
if (compares_unordered(lhs, rhs, true))
{
return false;
}
return !(lhs == rhs); return !(lhs == rhs);
} }
+1 -5
View File
@@ -68,11 +68,7 @@ target_compile_options(test_main PUBLIC
# Disable warning C4566: character represented by universal-character-name '\uFF01' # Disable warning C4566: character represented by universal-character-name '\uFF01'
# cannot be represented in the current code page (1252) # cannot be represented in the current code page (1252)
# Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated # Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated
# Disable warning C4702: unreachable code; wide_string_input_adapter::get_elements() $<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;$<$<CONFIG:Release>:/wd4702>>
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
# makes MSVC flag the code following its call in binary_reader.hpp
# as unreachable for that instantiation, in both Debug and Release
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702>
# https://github.com/nlohmann/json/issues/1114 # https://github.com/nlohmann/json/issues/1114
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj> $<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
+1 -1
View File
@@ -24,7 +24,7 @@ struct bad_allocator : std::allocator<T>
template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { } template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { }
template<class... Args> template<class... Args>
[[noreturn]] void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
throw std::bad_alloc(); throw std::bad_alloc();
} }
+14 -33
View File
@@ -369,7 +369,6 @@ TEST_CASE("lexicographical comparison operators")
SECTION("comparison: not equal") SECTION("comparison: not equal")
{ {
// check that two values compare unequal as expected // check that two values compare unequal as expected
// operator!= now means exactly !(a==b) without special cases for NaN/discarded
for (size_t i = 0; i < j_values.size(); ++i) for (size_t i = 0; i < j_values.size(); ++i)
{ {
for (size_t j = 0; j < j_values.size(); ++j) for (size_t j = 0; j < j_values.size(); ++j)
@@ -377,12 +376,25 @@ TEST_CASE("lexicographical comparison operators")
CAPTURE(i) CAPTURE(i)
CAPTURE(j) CAPTURE(j)
CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); if (json::compares_unordered(j_values[i], j_values[j], true))
{
// if two values compare unordered,
// check that the boolean comparison result is always false
CHECK_FALSE(j_values[i] != j_values[j]);
}
else
{
// otherwise, check that they compare according to their definition
// as the inverse of equal
CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]));
}
} }
} }
// compare with null pointer // compare with null pointer
const json j_null; const json j_null;
CHECK((j_null != nullptr) == false);
CHECK((nullptr != j_null) == false);
CHECK((j_null != nullptr) == !(j_null == nullptr)); CHECK((j_null != nullptr) == !(j_null == nullptr));
CHECK((nullptr != j_null) == !(nullptr == j_null)); CHECK((nullptr != j_null) == !(nullptr == j_null));
} }
@@ -582,34 +594,3 @@ TEST_CASE("lexicographical comparison operators")
} }
#endif #endif
} }
#if JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
{
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
// Verify that heterogeneous comparisons now work.
SECTION("string vs json")
{
std::string s = "string";
json j = "string";
CHECK(s == j);
CHECK(j == s);
CHECK_FALSE(s != j);
CHECK_FALSE(j != s);
}
SECTION("other heterogeneous types")
{
int i = 42;
json j = 42;
CHECK(i == j);
CHECK(j == i);
CHECK_FALSE(i != j);
CHECK_FALSE(j != i);
}
}
#endif
+1 -1
View File
@@ -278,7 +278,7 @@ TEST_CASE("constructors")
const auto t = j.get<std::tuple<int, float, std::string>>(); const auto t = j.get<std::tuple<int, float, std::string>>();
CHECK(std::get<0>(t) == j[0]); CHECK(std::get<0>(t) == j[0]);
CHECK(std::get<1>(t) == j[1]); CHECK(std::get<1>(t) == j[1]);
CHECK(std::get<2>(t) == j[2]); // CHECK(std::get<2>(t) == j[2]); // commented out due to CI issue, see https://github.com/nlohmann/json/pull/3985 and https://github.com/nlohmann/json/issues/4025
} }
SECTION("std::tuple tie") SECTION("std::tuple tie")
+1 -1
View File
@@ -942,7 +942,7 @@ TEST_CASE("iterators 2")
json j_expected{5, 4, 3, 2, 1}; json j_expected{5, 4, 3, 2, 1};
auto reversed = j | std::views::reverse; auto reversed = j | std::views::reverse;
CHECK(reversed == j_expected); CHECK(std::ranges::equal(reversed, j_expected));
} }
SECTION("transform") SECTION("transform")
+41
View File
@@ -54,6 +54,47 @@ TEST_CASE("Custom container non-member begin/end")
} }
struct MyContainerNonConstADL
{
char* data;
std::size_t size;
};
char* begin(MyContainerNonConstADL& c)
{
return c.data;
}
char* end(MyContainerNonConstADL& c)
{
return c.data + c.size; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
TEST_CASE("Custom container non-member non-const begin/end")
{
// Container with lvalue-only non-const ADL begin/end (bug reproduction)
char raw_data[] = "[1,2,3,4]";
MyContainerNonConstADL data{raw_data, sizeof(raw_data) - 1};
const json as_json = json::parse(data);
CHECK(as_json.at(0) == 1);
CHECK(as_json.at(1) == 2);
CHECK(as_json.at(2) == 3);
CHECK(as_json.at(3) == 4);
// Same container with accept()
CHECK(json::accept(data));
}
TEST_CASE("Custom container non-member begin/end, rvalue")
{
// Regression check: rvalue container parsing should still work
const json as_json = json::parse(MyContainer{"[1,2,3,4]"});
CHECK(as_json.at(0) == 1);
CHECK(as_json.at(1) == 2);
CHECK(as_json.at(2) == 3);
CHECK(as_json.at(3) == 4);
}
TEST_CASE("Custom container member begin/end") TEST_CASE("Custom container member begin/end")
{ {
struct MyContainer2 struct MyContainer2