Compare commits

..
Author SHA1 Message Date
Niels Lohmann 14558ba011 Merge branch 'develop' into claude/issue-5066-24ba30
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 22:22:36 +02:00
Niels Lohmann ccb419ca64 Fix clang-tidy and clang 6 in the #5066 regression test
ci_clang_tidy asked for emplace_back instead of push_back. The
push_back is the point of the test: #5066 is about the implicit
conversion from json to the vector's value type, which emplace_back
would bypass. Silence the check on that line.

clang 5 and 6 cannot instantiate std::variant<json> from libstdc++ 10's
<variant> ("cannot cast private base class"), which broke
ci_test_compilers_clang (6). Tested with the CI images: clang 7 to 11
compile and pass, including clang 11 with libstdc++ 10. Skip the runtime
check for clang before 7; the static_assert still runs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 18:22:16 +02:00
Niels Lohmann 4494890be6 Merge branch 'develop' into claude/issue-5066-24ba30
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:57:55 +02:00
Niels Lohmann c4469c9f82 Add regression test for converting json to std::variant<json> (#5066)
With 3.10.5, get<std::variant<json>>() was well-formed through the string
from_json overload, so the implicit conversion operator was a candidate
when converting json to std::variant<json>, and MSVC picked it over the
variant's converting constructor. The tightened constraints from #3427 and
#3604 (3.11.0) removed that path; this test guards against regressions.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 16:49:33 +02:00
6 changed files with 22 additions and 69 deletions
@@ -50,11 +50,9 @@ The default value is `0` (disabled — existing behavior is preserved).
```
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
element, and a single `[string, value]` pair *written as a braced list*, such as `{{"key", "value"}}`, which still
creates an object, are not affected. This exception is based on how the pair is written, not on the shape of its
value: an existing JSON value that happens to be a two-element array with a string as its first element, such as
`json arr = {"key", 42};`, is still copied by `json j{arr};` rather than turned into an object. The library's own
conversions are not affected either: for example, `std::tuple<int>{5}` still becomes `[5]`.
element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
`[5]`.
!!! note "ABI compatibility"
-9
View File
@@ -34,7 +34,6 @@ class json_ref
json_ref(std::initializer_list<json_ref> init)
: owned_value(init)
, braced_list(true)
{}
template <
@@ -70,17 +69,9 @@ class json_ref
return &** this;
}
/// whether the value was written as a braced list, such as {"key", 1},
/// rather than given as a value
bool is_braced_list() const noexcept
{
return braced_list;
}
private:
mutable value_type owned_value = nullptr;
value_type const* value_ref = nullptr;
bool braced_list = false;
};
} // namespace detail
-12
View File
@@ -1672,18 +1672,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool type_deduction = true,
value_t manual_type = value_t::array)
{
#if JSON_BRACE_INIT_COPY_SEMANTICS
// a single element that is a value rather than a braced list is
// copied or moved as is, whatever its content looks like
if (type_deduction && init.size() == 1 && !init.begin()->is_braced_list())
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}
#endif
// check if each element is an array with two elements whose first
// element is a string
bool is_an_object = std::all_of(init.begin(), init.end(),
-21
View File
@@ -20047,7 +20047,6 @@ class json_ref
json_ref(std::initializer_list<json_ref> init)
: owned_value(init)
, braced_list(true)
{}
template <
@@ -20083,17 +20082,9 @@ class json_ref
return &** this;
}
/// whether the value was written as a braced list, such as {"key", 1},
/// rather than given as a value
bool is_braced_list() const noexcept
{
return braced_list;
}
private:
mutable value_type owned_value = nullptr;
value_type const* value_ref = nullptr;
bool braced_list = false;
};
} // namespace detail
@@ -27762,18 +27753,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool type_deduction = true,
value_t manual_type = value_t::array)
{
#if JSON_BRACE_INIT_COPY_SEMANTICS
// a single element that is a value rather than a braced list is
// copied or moved as is, whatever its content looks like
if (type_deduction && init.size() == 1 && !init.begin()->is_braced_list())
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}
#endif
// check if each element is an array with two elements whose first
// element is a string
bool is_an_object = std::all_of(init.begin(), init.end(),
@@ -72,28 +72,6 @@ TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
CHECK(j7 == json::array({1, 2}));
}
SECTION("single-element brace initialization copies a pair-shaped array value (#5662)")
{
// a JSON value that happens to be a 2-element array whose first
// element is a string must still be copied, not turned into an
// object; only a braced list written in the source, such as the
// inner {"key", "value"} of {{"key", "value"}}, describes an object
json const pair_shaped = json::array({"key", 42});
json const j1{pair_shaped};
CHECK(j1.is_array());
CHECK(j1 == pair_shaped);
json const j2 = {pair_shaped};
CHECK(j2.is_array());
CHECK(j2 == pair_shaped);
// the same holds for an rvalue of the same shape
json const j3{json::array({"key", 42})};
CHECK(j3.is_array());
CHECK(j3 == pair_shaped);
}
SECTION("what the macro does not change")
{
// lists with more than one element are unaffected
+19
View File
@@ -766,6 +766,25 @@ TEST_CASE("regression tests 2")
CHECK(j == k);
}
#ifdef JSON_HAS_CPP_17
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
{
// std::variant<json> must not be retrievable via get<>(), because otherwise the
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
// converting constructor, routing a number through the string from_json overload
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
"std::variant<json> must not be retrievable via get<>()");
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
#if !(defined(__clang__) && __clang_major__ < 7)
// push_back, not emplace_back: #5066 needs the implicit conversion
// from json to the vector's value type
std::vector<std::variant<json>> v;
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
CHECK(std::get<0>(v[0]) == 1);
#endif
}
#endif
}
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")