Make cross-string-type basic_json conversion explicit without implicit conversions (#5591)

* Make cross-string-type basic_json conversion explicit without implicit conversions

The converting constructor from another basic_json specialization was
always implicit, so a value with a different string_t (std::wstring, a
string with a custom allocator, ...) silently converted into a temporary,
e.g. when passed to a function taking const nlohmann::json&. Such
conversions do not produce correct values (#3425), and
JSON_USE_IMPLICIT_CONVERSIONS=0 did not catch them.

When JSON_USE_IMPLICIT_CONVERSIONS is 0, the constructor is now explicit
if the string types differ. Specializations sharing a string type (json
and ordered_json, different serializers or object maps) stay implicitly
convertible, so the NLOHMANN_DEFINE_TYPE_* macros keep working with
nested json members. get<BasicJsonType>() constructs explicitly and
works in both modes.

Fixes #2649.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Construct explicitly in get_to() and to_json(std::optional)

With JSON_USE_IMPLICIT_CONVERSIONS=0 the conversion from a basic_json
with a different string type is now explicit, but two library paths
still assigned such a value implicitly and failed to compile inside the
library:

- get_to() with a basic_json target (the #2175 overload) did
  `v = *this`, so json(42).get_to(alt_json&) broke although
  get<alt_json>() works.
- to_json(BasicJsonType&, const std::optional<T>&) is constrained on
  std::is_constructible (which accepts the explicit constructor) but did
  `j = *opt`, so converting a std::optional<alt_json> into a json broke.

Both now construct the value explicitly, as get_impl() already does.

Also replace static_cast<bool>(JSON_USE_IMPLICIT_CONVERSIONS) with a
comparison: clang-tidy's modernize-use-bool-literals rejected the cast
of the integer literal the macro expands to, failing ci_clang_tidy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-10-04 11:46:52 +02:00
committed by GitHub
parent a212d3b2e4
commit 1df1e8a845
6 changed files with 144 additions and 10 deletions
@@ -294,7 +294,9 @@ void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_not
{
if (opt.has_value())
{
j = *opt;
// explicit construction, as the conversion from a basic_json with a different
// string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649)
j = BasicJsonType(*opt);
}
else
{
+34 -3
View File
@@ -1950,12 +1950,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
assert_invariant();
}
private:
/// whether a basic_json specialization can be converted implicitly into this one;
/// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share
/// the same string type (see https://github.com/nlohmann/json/issues/2649)
template<typename BasicJsonType>
using is_implicitly_convertible_basic_json = std::integral_constant < bool,
(JSON_USE_IMPLICIT_CONVERSIONS != 0)
|| std::is_same<typename BasicJsonType::string_t, string_t>::value >;
/// tag to select the constructor that performs the conversion from another basic_json specialization
struct convert_basic_json_tag {};
public:
/// @brief create a JSON value from an existing one
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
template < typename BasicJsonType,
detail::enable_if_t <
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
&& is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
basic_json(const BasicJsonType& val)
: basic_json(val, convert_basic_json_tag{})
{}
/// @brief create a JSON value from an existing one
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
template < typename BasicJsonType,
detail::enable_if_t <
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
&& !is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
explicit basic_json(const BasicJsonType& val)
: basic_json(val, convert_basic_json_tag{})
{}
private:
template<typename BasicJsonType>
basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/)
#if JSON_DIAGNOSTIC_POSITIONS
: start_position(val.start_pos()),
end_position(val.end_pos())
@@ -1975,6 +2005,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
assert_invariant();
}
public:
/// @brief create a container (array or object) from an initializer list
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
basic_json(initializer_list_t init,
@@ -2741,7 +2772,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
int > = 0 >
BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const
{
return *this;
return BasicJsonType(*this);
}
/*!
@@ -2880,7 +2911,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
int> = 0>
ValueType & get_to(ValueType& v) const
{
v = *this;
v = ValueType(*this);
return v;
}