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
4 changed files with 19 additions and 63 deletions
@@ -3821,11 +3821,6 @@ class binary_reader
if (0xC2 <= byte && byte <= 0xF7)
{
const auto second = get_bon8();
if (second == char_traits<char_type>::eof())
{
// the input ends inside a character or an integer
return unexpect_eof(input_format_t::bon8, "key");
}
unget_bon8(second);
if (is_bon8_continuation(second))
{
@@ -3924,12 +3919,6 @@ class binary_reader
// a lead byte ends the string if no continuation byte follows: it
// is then the first byte of an integer
const auto second = get_bon8();
if (second == char_traits<char_type>::eof())
{
// the input ends inside a character or an integer: either
// way, the message is incomplete
return unexpect_eof(input_format_t::bon8, "string");
}
if (!is_bon8_continuation(second))
{
unget_bon8(second);
-11
View File
@@ -16588,11 +16588,6 @@ class binary_reader
if (0xC2 <= byte && byte <= 0xF7)
{
const auto second = get_bon8();
if (second == char_traits<char_type>::eof())
{
// the input ends inside a character or an integer
return unexpect_eof(input_format_t::bon8, "key");
}
unget_bon8(second);
if (is_bon8_continuation(second))
{
@@ -16691,12 +16686,6 @@ class binary_reader
// a lead byte ends the string if no continuation byte follows: it
// is then the first byte of an integer
const auto second = get_bon8();
if (second == char_traits<char_type>::eof())
{
// the input ends inside a character or an integer: either
// way, the message is incomplete
return unexpect_eof(input_format_t::bon8, "string");
}
if (!is_bon8_continuation(second))
{
unget_bon8(second);
-41
View File
@@ -531,47 +531,6 @@ TEST_CASE("BON8")
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
}
SECTION("input that ends after a UTF-8 lead byte")
{
// the lead byte begins either a character or an integer; both are
// incomplete, so the lead byte must not end the string before it
for (const bool strict :
{
true, false
})
{
CAPTURE(strict)
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xF0}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xC3, 0xA9, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
}
}
SECTION("a message that is cut off is not read as a shorter value")
{
const json values = {"\xC3\xA9", "a\xE2\x82\xAC", "\xF0\x9F\x98\x80\xC3\xA9", {"a\xC3\xA9"}, {{"\xC3\xA9", "\xE2\x82\xAC"}}, {{"a", {"b\xC3\xA9", 1}}}};
for (const auto& j : values)
{
const bytes message = json::to_bon8(j);
for (std::size_t length = 0; length < message.size(); ++length)
{
CAPTURE(j)
CAPTURE(length)
bytes prefix = message;
prefix.resize(length);
CHECK(json::from_bon8(prefix, false, false).is_discarded());
// a stream is read byte by byte rather than in bulk
std::istringstream stream(str(prefix));
CHECK(json::from_bon8(stream, false, false).is_discarded());
}
}
}
SECTION("invalid UTF-8")
{
// overlong
+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")