Compare commits

..

6 Commits

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

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

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

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 16:26:09 +02:00
Niels Lohmann d23806a327 Fix clang-tidy misc-const-correctness in heterogeneous sentinel test
json_str is only read via .data()/.size() and never reassigned, so
clang-tidy correctly flags it as const-able. Verified against the exact
CI job (silkeh/clang:dev, ci_clang_tidy target) by running clang-tidy
directly on this file plus the five binary-format sentinel tests
touched by prior commits; all are now clean.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 10:06:11 +02:00
Niels Lohmann fa9741f0ce Fix clang-tidy hicpp-named-parameter and misc-const-correctness
- Drop the unused reversed-order operator!= overload from
  utils::istreambuf_sentinel (only iterator != sentinel is ever
  evaluated) and name the remaining friend's sentinel parameter, fixing
  hicpp-named-parameter/readability-named-parameter.
- Mark the istreambuf_iterator first/last helper variable const in the
  five binary-format sentinel tests, fixing misc-const-correctness.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 00:10:23 +02:00
Niels Lohmann da95d1184e Fix -Wunneeded-internal-declaration for CustomSentinel in test
CustomSentinel lives in an anonymous namespace (internal linkage), and
the library's parse loop only ever evaluates the iterator-first
direction (it != last), so the reversed-order friend operator!= was
never referenced. Clang's -Weverything flags such unused internal
declarations as an error. Drop the unused overload; the used direction
is enough to satisfy can_compare_ne's either-order detection.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 23:25:21 +02:00
Niels Lohmann 146e5e0bc7 Merge iterator+sentinel overloads and fix ambiguity/CI issues
Address PR review feedback and CI failures:

- Merge the separate same-type and sentinel-type iterator overloads of
  parse(), accept(), sax_parse(), and the five from_* binary deserializers
  into a single overload with SentinelType defaulted to IteratorType,
  as suggested in review. Applied the same simplification to the
  detail::input_adapter() free functions.
- Fix a latent ambiguity: some compilers (e.g. GCC 4.8) unreliably SFINAE
  the operator!= detection for std::nullptr_t against container/string
  types, making calls like parse(s, nullptr, ...) ambiguous with the
  compatible-input overload. can_compare_ne now explicitly excludes
  std::nullptr_t as a SentinelType.
- Use a named enable_if_t template parameter instead of an unnamed
  function parameter for the SFINAE guard, fixing a clang-tidy
  hicpp-named-parameter/readability-named-parameter failure.
- Update parse.md, accept.md, sax_parse.md, and the five from_*.md pages
  to document the merged overload instead of separate (2)/(3) overloads,
  also fixing an over-160-char line that broke the documentation
  style_check CI job.
- Rework the BSON iterator+sentinel test to parse a BSON file already
  present in the test suite instead of writing/deleting a temp file.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 23:15:17 +02:00
Niels Lohmann 2269656bc6 Add iterator+sentinel tests and docs for binary deserializers
This commit extends the C++20 ranges support (iterator+sentinel pairs) to the
binary format deserializers from_cbor, from_msgpack, from_ubjson, from_bjdata,
and from_bson, matching what was already done for parse(), accept(), and
sax_parse().

Changes:
- Add istreambuf_sentinel helper to test_utils.hpp for EOF detection in tests
- Add 5 new test cases that read binary files directly via
  std::istreambuf_iterator<char> + sentinel, without pre-buffering
- Update documentation for all 5 from_* functions to document overload (3)
  with SentinelType parameter
- All tests pass; verified against existing test suite data
- Fix potential buffer over-read warning in heterogeneous iterator test

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 19:50:04 +02:00
11 changed files with 12 additions and 496 deletions
@@ -51,38 +51,6 @@ represent a byte array in modern C++.
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
#### Custom BinaryType behavior
When a custom `BinaryType` is configured (other than the default `#!cpp std::vector<std::uint8_t>`), you can assign
values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values
rather than arrays:
```cpp
using custom_json = nlohmann::basic_json<
nlohmann::ordered_map, // ObjectType
std::vector, // ArrayType
std::string, // StringType
bool, // BooleanType
std::int64_t, // NumberIntegerType
std::uint64_t, // NumberUnsignedType
double, // NumberFloatType
std::allocator, // AllocatorType
nlohmann::adl_serializer,
std::vector<std::byte> // Custom BinaryType
>;
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
custom_json j = data; // Creates a binary value, not an array
assert(j.is_binary());
// Round-tripping works seamlessly
auto extracted = j.get<std::vector<std::byte>>();
assert(extracted == data);
```
This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations.
The default `nlohmann::json` continues to treat `#!cpp std::vector<std::uint8_t>` as arrays for backward compatibility.
#### Storage
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
@@ -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
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
which defines `JSON_DIAGNOSTICS` accordingly. Note this only applies when building the
library from source — see the pre-installed-package caveat on that page.
which defines `JSON_DIAGNOSTICS` accordingly.
## 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}}
```
`#!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
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]
```
!!! 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
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.
!!! 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`
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default.
@@ -19,7 +19,6 @@
#include <unordered_map> // unordered_map
#include <utility> // pair, declval
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
@@ -333,7 +332,6 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_basic_json<ConstructibleArrayType>::value,
int > = 0 >
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
@@ -379,25 +377,6 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
{
if (j.is_binary())
{
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
}
else if (j.is_array())
{
from_json_array_impl(j, bin, priority_tag<3> {});
}
else
{
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
}
}
template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
@@ -177,11 +177,8 @@ struct external_constructor<value_t::array>
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
, int > = 0 >
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
{
using std::begin;
@@ -221,25 +218,6 @@ struct external_constructor<value_t::array>
j.set_parents();
j.assert_invariant();
}
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
}
j.assert_invariant();
}
#endif
};
template<>
@@ -377,44 +355,19 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
,
!is_basic_json<CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
{
external_constructor<value_t::array>::construct(j, arr);
}
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
&& !is_compatible_string_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_compatible_object_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_basic_json<std::remove_cvref_t<T>>::value, int > = 0 >
inline void to_json(BasicJsonType& j, T && arr)
{
external_constructor<value_t::array>::construct(j, std::forward<T>(arr));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)
{
external_constructor<value_t::binary>::construct(j, bin);
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
{
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
+1 -83
View File
@@ -13,15 +13,11 @@
#include <tuple> // tuple
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
#include <utility> // declval
#include <vector> // vector
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
#include <cstddef> // byte
#endif
#include <nlohmann/detail/iterators/iterator_traits.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <optional> // optional
#endif
#include <nlohmann/detail/meta/call_std/begin.hpp>
#include <nlohmann/detail/meta/call_std/end.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
@@ -454,51 +450,6 @@ struct is_constructible_string_type
value_type_t, laundered_type >>::value;
};
// Forward declarations: iteration_proxy.hpp includes this file, so we cannot
// include it here.
template<typename IteratorType> class iteration_proxy;
template<typename IteratorType> class iteration_proxy_value;
// Identifies nlohmann's internal iteration-proxy types. These must be excluded
// before evaluating any std::ranges concept to avoid circular constraints.
template<typename T> struct is_iteration_proxy_type : std::false_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy<T>> : std::true_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy_value<T>> : std::true_type {};
// In C++26, std::optional satisfies std::ranges::view; exclude it so the
// range-view overload does not hijack the optional serializer.
#ifdef JSON_HAS_CPP_17
template<typename T> struct is_range_view_optional_type : std::false_type {};
template<typename T> struct is_range_view_optional_type<std::optional<T>> : std::true_type {};
#else
template<typename T> struct is_range_view_optional_type : std::false_type {};
#endif
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
// SafeToCheck guards against types that trigger circular constraints when
// std::ranges::view<T> is evaluated on GCC 12 / libstdc++ 12:
// - iteration_proxy / iteration_proxy_value directly
// - views wrapping the above (e.g. owning_view<iteration_proxy<...>>)
// - views wrapping basic_json (e.g. ref_view<json>) — same circularity
// via json's constructors → is_compatible_array_type → here
// nlohmann's plain range_value_t (iterator_traits-based) is safe to call
// before any std::ranges concept is touched, so we use it for the checks.
template < typename T, bool SafeToCheck =
!is_iteration_proxy_type<T>::value &&
!is_iteration_proxy_type<detected_t<range_value_t, T>>::value &&
!is_basic_json<detected_t<range_value_t, T>>::value &&
!is_range_view_optional_type<T>::value >
struct is_compatible_range_view : std::false_type {};
template<typename T>
struct is_compatible_range_view<T, true>
: std::bool_constant<std::ranges::view<T>> {};
#endif
template<typename BasicJsonType, typename CompatibleArrayType, typename = void>
struct is_compatible_array_type_impl : std::false_type {};
@@ -510,38 +461,13 @@ struct is_compatible_array_type_impl <
is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&
// special case for types like std::filesystem::path whose iterator's value_type are themselves
// c.f. https://github.com/nlohmann/json/pull/3073
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value
// When range-view support is enabled, std::ranges::view types (e.g. std::string_view,
// filter_view) can match BOTH this iterator-based specialization AND the view-based one
// below, causing ambiguity. Exclude views here so the two specializations are mutually
// exclusive: this one handles plain iterable containers, the other handles views.
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
>>
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >>
{
static constexpr bool value =
is_constructible<BasicJsonType,
range_value_t<CompatibleArrayType>>::value;
};
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type_impl <
BasicJsonType, CompatibleArrayType,
enable_if_t < is_compatible_range_view<CompatibleArrayType>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, char>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, wchar_t>::value >>
{
// CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t
// is safe and correctly handles C++20 iterators that may lack classic iterator_traits.
static constexpr bool value =
is_constructible<BasicJsonType,
std::ranges::range_value_t<CompatibleArrayType>>::value;
};
#endif
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type
: is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
@@ -633,14 +559,6 @@ template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_binary_type
{
static constexpr bool value =
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
};
template<typename BasicJsonType, typename CompatibleReferenceType>
struct is_compatible_reference_type_impl
{
+4 -154
View File
@@ -189,7 +189,6 @@
#include <unordered_map> // unordered_map
#include <utility> // pair, declval
#include <valarray> // valarray
#include <vector> // vector
// #include <nlohmann/detail/exceptions.hpp>
// __ _____ _____ _____
@@ -3593,7 +3592,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <tuple> // tuple
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
#include <utility> // declval
#include <vector> // vector
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
#include <cstddef> // byte
#endif
@@ -3665,9 +3663,6 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <optional> // optional
#endif
// #include <nlohmann/detail/meta/call_std/begin.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
@@ -4217,51 +4212,6 @@ struct is_constructible_string_type
value_type_t, laundered_type >>::value;
};
// Forward declarations: iteration_proxy.hpp includes this file, so we cannot
// include it here.
template<typename IteratorType> class iteration_proxy;
template<typename IteratorType> class iteration_proxy_value;
// Identifies nlohmann's internal iteration-proxy types. These must be excluded
// before evaluating any std::ranges concept to avoid circular constraints.
template<typename T> struct is_iteration_proxy_type : std::false_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy<T>> : std::true_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy_value<T>> : std::true_type {};
// In C++26, std::optional satisfies std::ranges::view; exclude it so the
// range-view overload does not hijack the optional serializer.
#ifdef JSON_HAS_CPP_17
template<typename T> struct is_range_view_optional_type : std::false_type {};
template<typename T> struct is_range_view_optional_type<std::optional<T>> : std::true_type {};
#else
template<typename T> struct is_range_view_optional_type : std::false_type {};
#endif
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
// SafeToCheck guards against types that trigger circular constraints when
// std::ranges::view<T> is evaluated on GCC 12 / libstdc++ 12:
// - iteration_proxy / iteration_proxy_value directly
// - views wrapping the above (e.g. owning_view<iteration_proxy<...>>)
// - views wrapping basic_json (e.g. ref_view<json>) — same circularity
// via json's constructors → is_compatible_array_type → here
// nlohmann's plain range_value_t (iterator_traits-based) is safe to call
// before any std::ranges concept is touched, so we use it for the checks.
template < typename T, bool SafeToCheck =
!is_iteration_proxy_type<T>::value &&
!is_iteration_proxy_type<detected_t<range_value_t, T>>::value &&
!is_basic_json<detected_t<range_value_t, T>>::value &&
!is_range_view_optional_type<T>::value >
struct is_compatible_range_view : std::false_type {};
template<typename T>
struct is_compatible_range_view<T, true>
: std::bool_constant<std::ranges::view<T>> {};
#endif
template<typename BasicJsonType, typename CompatibleArrayType, typename = void>
struct is_compatible_array_type_impl : std::false_type {};
@@ -4273,38 +4223,13 @@ struct is_compatible_array_type_impl <
is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&
// special case for types like std::filesystem::path whose iterator's value_type are themselves
// c.f. https://github.com/nlohmann/json/pull/3073
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value
// When range-view support is enabled, std::ranges::view types (e.g. std::string_view,
// filter_view) can match BOTH this iterator-based specialization AND the view-based one
// below, causing ambiguity. Exclude views here so the two specializations are mutually
// exclusive: this one handles plain iterable containers, the other handles views.
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
>>
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >>
{
static constexpr bool value =
is_constructible<BasicJsonType,
range_value_t<CompatibleArrayType>>::value;
};
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type_impl <
BasicJsonType, CompatibleArrayType,
enable_if_t < is_compatible_range_view<CompatibleArrayType>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, char>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, wchar_t>::value >>
{
// CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t
// is safe and correctly handles C++20 iterators that may lack classic iterator_traits.
static constexpr bool value =
is_constructible<BasicJsonType,
std::ranges::range_value_t<CompatibleArrayType>>::value;
};
#endif
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type
: is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
@@ -4396,14 +4321,6 @@ template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_binary_type
{
static constexpr bool value =
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
};
template<typename BasicJsonType, typename CompatibleReferenceType>
struct is_compatible_reference_type_impl
{
@@ -5547,7 +5464,6 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_basic_json<ConstructibleArrayType>::value,
int > = 0 >
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
@@ -5593,25 +5509,6 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
{
if (j.is_binary())
{
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
}
else if (j.is_array())
{
from_json_array_impl(j, bin, priority_tag<3> {});
}
else
{
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
}
}
template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
@@ -6308,11 +6205,8 @@ struct external_constructor<value_t::array>
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
, int > = 0 >
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
{
using std::begin;
@@ -6352,25 +6246,6 @@ struct external_constructor<value_t::array>
j.set_parents();
j.assert_invariant();
}
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
}
j.assert_invariant();
}
#endif
};
template<>
@@ -6508,44 +6383,19 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
,
!is_basic_json<CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
{
external_constructor<value_t::array>::construct(j, arr);
}
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
&& !is_compatible_string_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_compatible_object_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_basic_json<std::remove_cvref_t<T>>::value, int > = 0 >
inline void to_json(BasicJsonType& j, T && arr)
{
external_constructor<value_t::array>::construct(j, std::forward<T>(arr));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)
{
external_constructor<value_t::binary>::construct(j, bin);
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
{
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
-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&);
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
"[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")
-74
View File
@@ -1136,40 +1136,6 @@ TEST_CASE("regression tests 2")
CHECK((decoded == json_4804::array()));
}
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
{
// Test that assigning a custom BinaryType directly creates a binary value, not an array
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
const json_4804 j = original;
CHECK(j.is_binary());
CHECK(!j.is_array());
// Test round-tripping: extracting the binary value back as the custom container type
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted == original);
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
CHECK(default_json.is_array());
CHECK(!default_json.is_binary());
}
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
{
// Test that extracting a custom BinaryType from a parsed JSON array still works
// (not just from a binary-typed node)
const auto j = json_4804::parse("[1,2,3]");
CHECK(j.is_array());
CHECK(!j.is_binary());
// Extracting as custom BinaryType should work from arrays
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted.size() == 3);
CHECK(extracted[0] == std::byte{1});
CHECK(extracted[1] == std::byte{2});
CHECK(extracted[2] == std::byte{3});
}
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
{
const json jval{};
@@ -1199,46 +1165,6 @@ TEST_CASE("regression tests 2")
}
#endif
#if JSON_HAS_RANGES && !defined(__MINGW32__)
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
{
std::vector<int> nums{1, 2, 37, 42, 21};
auto filteredNums = nums | std::views::filter([](int i)
{
return i > 10;
});
json const j(filteredNums);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
// owning_view is not available in libstdc++ < 12
#if JSON_HAS_RANGES && !defined(__MINGW32__) && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
{
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
{
return i > 10;
}));
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
#if JSON_HAS_RANGES && !defined(__MINGW32__)
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
{
std::vector<int> nums{1, 2, 3};
auto t = nums | std::views::transform([](int i) noexcept
{
return i * 2;
});
json const j(t);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({2, 4, 6}));
}
#endif
}
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+3 -4
View File
@@ -216,16 +216,15 @@ TEST_CASE("Parse with heterogeneous iterator and sentinel types")
// 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 auto len = static_cast<std::iter_difference_t<std::string::const_iterator>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
std::counted_iterator first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
std::counted_iterator first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
#endif