mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 21:20:30 +00:00
Fix key types convertible to std::string_view breaking lookups
Since #4958, a key type implicitly convertible to std::string_view was accepted by is_usable_as_basic_json_key_type without checking that the object's comparator can actually compare object_t::key_type with that key type. The key was then forwarded unchanged to the underlying map, so const operator[], at, find, count, contains, erase and value failed to compile (a hard error inside <map>) for a key convertible only to std::string_view, and value() rejected such keys outright. For keys convertible to both std::string and std::string_view, the KeyType&& templates now won overload resolution over the object_t::key_type overloads and then failed the same way, a regression from 3.12.0. Only the non-const operator[] worked, because it uses emplace(), which constructs a std::string from the key explicitly. ordered_json was not affected, since ordered_map checks comparability itself. Add a trait, is_string_view_convertible_key_type, that recognizes a key type that is convertible to std::string_view but not directly comparable with the object's key type, provided std::string_view itself is comparable with it. at(), operator[], find(), count(), contains(), erase() and value() now route such keys through a new lookup_key() helper that converts them to std::string_view before they reach the object, matching how the object's transparent comparator already supports std::string_view lookups. Fixes #5663. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -1892,4 +1892,115 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("keys convertible to std::string_view work with all lookup functions (regression test for #5663)")
|
||||
{
|
||||
// a key type convertible only to std::string_view: the case #4958 added
|
||||
// support for, but only the non-const operator[] compiled with it
|
||||
struct ViewKey
|
||||
{
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
|
||||
// a key type convertible to both std::string and std::string_view: with
|
||||
// 3.12.0, such a key worked with at, the const operator[], find, count and
|
||||
// contains via the conversion to std::string; #4958 made the KeyType&&
|
||||
// templates win overload resolution for it instead, and those then failed
|
||||
struct DualKey
|
||||
{
|
||||
operator std::string() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
|
||||
SECTION("nlohmann::json")
|
||||
{
|
||||
using json = nlohmann::json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("nlohmann::ordered_json")
|
||||
{
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user