mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 15:07:13 +00:00
Make std::hash<basic_json> consistent with operator== for numbers
operator== converts between number_integer, number_unsigned, and number_float before comparing, so json(0), json(0U), and json(0.0) all compare equal. hash() folded the specific value_t into the result for each of the three numeric cases, giving each a distinct hash and breaking the standard Hash requirement that a == b implies hash(a) == hash(b). A std::unordered_set could therefore hold all three as separate elements even though they compare equal. hash() now treats all three numeric variants the same way: it converts the value to number_float_t and combines it with a single shared type tag, so any two numbers operator== considers equal hash identically regardless of which internal type actually holds them. Updated the accompanying test to check this consistency directly (including via an actual unordered_set) instead of asserting that 0, 0U, and 0.0 hash differently, since that assumption was the bug. Also corrected the function's own doc comment and the std::hash API docs, which described the old behavior as intended. Fixes #5400 Signed-off-by: Afonso Januário <afonso-januario@hotmail.com> Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
367336c83d
commit
c66be708eb
5 files changed
+53
-40
No files matched your search
+22
-8
@@ -14,6 +14,7 @@ using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -91,6 +92,9 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
// Collect hashes for different JSON values and make sure that they are distinct
|
||||
// We cannot compare against fixed values, because the implementation of
|
||||
// std::hash may differ between compilers.
|
||||
//
|
||||
// numbers that compare equal under operator== (0 == 0U == 0.0) must hash
|
||||
// equally, so they are only inserted once below and checked separately.
|
||||
|
||||
std::set<std::size_t> hashes;
|
||||
|
||||
@@ -107,10 +111,7 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
|
||||
// number
|
||||
hashes.insert(std::hash<json> {}(json(0)));
|
||||
hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0))));
|
||||
|
||||
hashes.insert(std::hash<json> {}(json(-1)));
|
||||
hashes.insert(std::hash<json> {}(json(0.0)));
|
||||
hashes.insert(std::hash<json> {}(json(42.23)));
|
||||
|
||||
// array
|
||||
@@ -132,7 +133,20 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
// discarded
|
||||
hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
|
||||
|
||||
CHECK(hashes.size() == 21);
|
||||
CHECK(hashes.size() == 19);
|
||||
|
||||
// numbers that compare equal under operator== must hash equally,
|
||||
// regardless of which of number_integer, number_unsigned, or
|
||||
// number_float actually holds the value
|
||||
CHECK(json(0) == json(static_cast<unsigned>(0)));
|
||||
CHECK(json(0) == json(0.0));
|
||||
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(static_cast<unsigned>(0))));
|
||||
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(0.0)));
|
||||
CHECK(std::hash<json> {}(json(-1)) == std::hash<json> {}(json(-1.0)));
|
||||
|
||||
// a std::unordered_set relies on this same consistency between == and hash
|
||||
std::unordered_set<json> numbers {json(0), json(static_cast<unsigned>(0)), json(0.0)};
|
||||
CHECK(numbers.size() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
@@ -156,10 +170,7 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
|
||||
// number
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
||||
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
|
||||
|
||||
// array
|
||||
@@ -181,7 +192,10 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
// discarded
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
|
||||
|
||||
CHECK(hashes.size() == 21);
|
||||
CHECK(hashes.size() == 19);
|
||||
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(0.0)));
|
||||
}
|
||||
|
||||
TEST_CASE("hash of deeply nested values")
|
||||
|
||||
Reference in new issue
Block a user