From 5b229f4cce957136dfd5fd34beeb9ef0336ae958 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 14 Jul 2020 14:31:19 +0200 Subject: [PATCH 1/5] :zap: hash function without allocation --- include/nlohmann/detail/hash.hpp | 93 ++++++++++++++++++++++++++++++ include/nlohmann/json.hpp | 5 +- single_include/nlohmann/json.hpp | 99 +++++++++++++++++++++++++++++++- 3 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 include/nlohmann/detail/hash.hpp diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp new file mode 100644 index 000000000..b0be7fb26 --- /dev/null +++ b/include/nlohmann/detail/hash.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include // hash + +namespace nlohmann +{ +namespace detail +{ + +std::size_t combine(std::size_t seed, std::size_t h) +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +template +std::size_t hash(const BasicJsonType& j) +{ + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::discarded: + return combine(static_cast(j.type()), 0); + + case BasicJsonType::value_t::object: + { + auto seed = combine(static_cast(j.type()), j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(static_cast(j.type()), j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash {}(j.template get_ref()); + return combine(static_cast(j.type()), h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::number_unsigned: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::number_float: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::binary: + { + auto seed = combine(static_cast(j.type()), j.get_binary().size()); + seed = combine(seed, j.get_binary().subtype()); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash {}(byte)); + } + return seed; + } + } + + return 0; +} + +} // namespace detail +} // namespace nlohmann diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 7c2cf2b16..1a45b7761 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -51,6 +51,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -8698,9 +8699,7 @@ struct hash */ std::size_t operator()(const nlohmann::json& j) const { - // a naive hashing via the string representation - const auto& h = hash(); - return h(j.dump()); + return nlohmann::detail::hash(j); } }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 82c0d4847..9a9b3a2f3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4442,6 +4442,101 @@ class byte_container_with_subtype : public BinaryType // #include +// #include + + +#include // hash + +namespace nlohmann +{ +namespace detail +{ + +std::size_t combine(std::size_t seed, std::size_t h) +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +template +std::size_t hash(const BasicJsonType& j) +{ + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::discarded: + return combine(static_cast(j.type()), 0); + + case BasicJsonType::value_t::object: + { + auto seed = combine(static_cast(j.type()), j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(static_cast(j.type()), j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash {}(j.template get_ref()); + return combine(static_cast(j.type()), h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::number_unsigned: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::number_float: + { + const auto h = std::hash {}(j.template get()); + return combine(static_cast(j.type()), h); + } + + case nlohmann::detail::value_t::binary: + { + auto seed = combine(static_cast(j.type()), j.get_binary().size()); + seed = combine(seed, j.get_binary().subtype()); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash {}(byte)); + } + return seed; + } + } + + return 0; +} + +} // namespace detail +} // namespace nlohmann + // #include @@ -24686,9 +24781,7 @@ struct hash */ std::size_t operator()(const nlohmann::json& j) const { - // a naive hashing via the string representation - const auto& h = hash(); - return h(j.dump()); + return nlohmann::detail::hash(j); } }; From 33b0bed7fe6e7e64aca0b0ba5c49001b0123cb7d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 14 Jul 2020 14:34:55 +0200 Subject: [PATCH 2/5] :green_heart: fix compilation --- include/nlohmann/detail/hash.hpp | 3 ++- single_include/nlohmann/json.hpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp index b0be7fb26..404a2bd3c 100644 --- a/include/nlohmann/detail/hash.hpp +++ b/include/nlohmann/detail/hash.hpp @@ -1,5 +1,6 @@ #pragma once +#include // size_t #include // hash namespace nlohmann @@ -19,7 +20,7 @@ std::size_t hash(const BasicJsonType& j) switch (j.type()) { case BasicJsonType::value_t::null: - case BasicJsonType::discarded: + case BasicJsonType::value_t::discarded: return combine(static_cast(j.type()), 0); case BasicJsonType::value_t::object: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9a9b3a2f3..6a7a9ff41 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4445,6 +4445,7 @@ class byte_container_with_subtype : public BinaryType // #include +#include // size_t #include // hash namespace nlohmann @@ -4464,7 +4465,7 @@ std::size_t hash(const BasicJsonType& j) switch (j.type()) { case BasicJsonType::value_t::null: - case BasicJsonType::discarded: + case BasicJsonType::value_t::discarded: return combine(static_cast(j.type()), 0); case BasicJsonType::value_t::object: From 9449dfcc6a48493877c81e06ae6116ac05f76385 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 15 Jul 2020 09:27:01 +0200 Subject: [PATCH 3/5] :green_heart: add test cases for hash --- include/nlohmann/detail/hash.hpp | 24 ++++++--- single_include/nlohmann/json.hpp | 24 ++++++--- test/CMakeLists.txt | 1 + test/Makefile | 1 + test/src/unit-hash.cpp | 90 ++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 test/src/unit-hash.cpp diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp index 404a2bd3c..a4aaae4a3 100644 --- a/include/nlohmann/detail/hash.hpp +++ b/include/nlohmann/detail/hash.hpp @@ -8,7 +8,7 @@ namespace nlohmann namespace detail { -std::size_t combine(std::size_t seed, std::size_t h) +std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; @@ -17,6 +17,11 @@ std::size_t combine(std::size_t seed, std::size_t h) template std::size_t hash(const BasicJsonType& j) { + using string_t = typename BasicJsonType::string_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + switch (j.type()) { case BasicJsonType::value_t::null: @@ -28,7 +33,7 @@ std::size_t hash(const BasicJsonType& j) auto seed = combine(static_cast(j.type()), j.size()); for (const auto& element : j.items()) { - const auto h = std::hash {}(element.key()); + const auto h = std::hash {}(element.key()); seed = combine(seed, h); seed = combine(seed, hash(element.value())); } @@ -47,7 +52,7 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::string: { - const auto h = std::hash {}(j.template get_ref()); + const auto h = std::hash {}(j.template get_ref()); return combine(static_cast(j.type()), h); } @@ -59,25 +64,27 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::number_integer: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::number_unsigned: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::number_float: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::binary: { auto seed = combine(static_cast(j.type()), j.get_binary().size()); + const auto h = std::hash {}(j.get_binary().has_subtype()); + seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); for (const auto byte : j.get_binary()) { @@ -85,9 +92,10 @@ std::size_t hash(const BasicJsonType& j) } return seed; } - } - return 0; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } } } // namespace detail diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6a7a9ff41..f5a6d8fea 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4453,7 +4453,7 @@ namespace nlohmann namespace detail { -std::size_t combine(std::size_t seed, std::size_t h) +std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; @@ -4462,6 +4462,11 @@ std::size_t combine(std::size_t seed, std::size_t h) template std::size_t hash(const BasicJsonType& j) { + using string_t = typename BasicJsonType::string_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + switch (j.type()) { case BasicJsonType::value_t::null: @@ -4473,7 +4478,7 @@ std::size_t hash(const BasicJsonType& j) auto seed = combine(static_cast(j.type()), j.size()); for (const auto& element : j.items()) { - const auto h = std::hash {}(element.key()); + const auto h = std::hash {}(element.key()); seed = combine(seed, h); seed = combine(seed, hash(element.value())); } @@ -4492,7 +4497,7 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::string: { - const auto h = std::hash {}(j.template get_ref()); + const auto h = std::hash {}(j.template get_ref()); return combine(static_cast(j.type()), h); } @@ -4504,25 +4509,27 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::number_integer: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::number_unsigned: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::number_float: { - const auto h = std::hash {}(j.template get()); + const auto h = std::hash {}(j.template get()); return combine(static_cast(j.type()), h); } case nlohmann::detail::value_t::binary: { auto seed = combine(static_cast(j.type()), j.get_binary().size()); + const auto h = std::hash {}(j.get_binary().has_subtype()); + seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); for (const auto byte : j.get_binary()) { @@ -4530,9 +4537,10 @@ std::size_t hash(const BasicJsonType& j) } return seed; } - } - return 0; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } } } // namespace detail diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9146d43b7..91ff5a21c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -112,6 +112,7 @@ set(files src/unit-deserialization.cpp src/unit-element_access1.cpp src/unit-element_access2.cpp + src/unit-hash.cpp src/unit-inspection.cpp src/unit-items.cpp src/unit-iterators1.cpp diff --git a/test/Makefile b/test/Makefile index bcf8de7de..b01fa8720 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,6 +10,7 @@ SOURCES = src/unit.cpp \ src/unit-algorithms.cpp \ src/unit-allocator.cpp \ src/unit-alt-string.cpp \ + src/unit-assert_macro.cpp \ src/unit-bson.cpp \ src/unit-capacity.cpp \ src/unit-cbor.cpp \ diff --git a/test/src/unit-hash.cpp b/test/src/unit-hash.cpp new file mode 100644 index 000000000..f80a65966 --- /dev/null +++ b/test/src/unit-hash.cpp @@ -0,0 +1,90 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.8.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using json = nlohmann::json; + +TEST_CASE("hash") +{ + SECTION("null") + { + CHECK(std::hash {}(json(nullptr)) == 2654435769U); + } + + SECTION("boolean") + { + CHECK(std::hash {}(json(true)) == 2654436031U); + CHECK(std::hash {}(json(false)) == 2654436030U); + } + + SECTION("string") + { + CHECK(std::hash {}(json("")) == 11160318156688833227U); + CHECK(std::hash {}(json("foo")) == 910203211069189493U); + } + + SECTION("number") + { + CHECK(std::hash {}(json(int(0))) == 2654436095U); + CHECK(std::hash {}(json(unsigned(0))) == 2654436156U); + + CHECK(std::hash {}(json(-1)) == 2654436092U); + CHECK(std::hash {}(json(0.0)) == 2654436221U); + CHECK(std::hash {}(json(42.23)) == 4631140164097181104U); + } + + SECTION("array") + { + CHECK(std::hash {}(json::array()) == 2654435899U); + CHECK(std::hash {}(json::array({1, 2, 3})) == 717272658337467U); + } + + SECTION("object") + { + CHECK(std::hash {}(json::object()) == 2654435832U); + CHECK(std::hash {}(json::object({{"foo", "bar"}})) == 4042265434648078139U); + } + + SECTION("binary") + { + CHECK(std::hash {}(json::binary({})) == 11093832941624U); + CHECK(std::hash {}(json::binary({}, 0)) == 11093832941691U); + CHECK(std::hash {}(json::binary({}, 42)) == 11093832941581U); + CHECK(std::hash {}(json::binary({1, 2, 3})) == 3005324138949694928U); + CHECK(std::hash {}(json::binary({1, 2, 3}, 0)) == 3005324138988516582U); + CHECK(std::hash {}(json::binary({1, 2, 3}, 42)) == 3005324138986241627U); + } + + SECTION("discarded") + { + CHECK(std::hash {}(json(json::value_t::discarded)) == 2654436338U); + } +} \ No newline at end of file From 496ddd127c95b8e424408b8299e51ea04675e17e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 15 Jul 2020 12:14:30 +0200 Subject: [PATCH 4/5] :green_heart: make test deterministic --- test/src/unit-hash.cpp | 84 ++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/test/src/unit-hash.cpp b/test/src/unit-hash.cpp index f80a65966..fddaceb98 100644 --- a/test/src/unit-hash.cpp +++ b/test/src/unit-hash.cpp @@ -32,59 +32,53 @@ SOFTWARE. #include using json = nlohmann::json; +#include + TEST_CASE("hash") { - SECTION("null") - { - CHECK(std::hash {}(json(nullptr)) == 2654435769U); - } + // 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. - SECTION("boolean") - { - CHECK(std::hash {}(json(true)) == 2654436031U); - CHECK(std::hash {}(json(false)) == 2654436030U); - } + std::set hashes; - SECTION("string") - { - CHECK(std::hash {}(json("")) == 11160318156688833227U); - CHECK(std::hash {}(json("foo")) == 910203211069189493U); - } + // null + hashes.insert(std::hash {}(json(nullptr))); - SECTION("number") - { - CHECK(std::hash {}(json(int(0))) == 2654436095U); - CHECK(std::hash {}(json(unsigned(0))) == 2654436156U); + // boolean + hashes.insert(std::hash {}(json(true))); + hashes.insert(std::hash {}(json(false))); - CHECK(std::hash {}(json(-1)) == 2654436092U); - CHECK(std::hash {}(json(0.0)) == 2654436221U); - CHECK(std::hash {}(json(42.23)) == 4631140164097181104U); - } + // string + hashes.insert(std::hash {}(json(""))); + hashes.insert(std::hash {}(json("foo"))); - SECTION("array") - { - CHECK(std::hash {}(json::array()) == 2654435899U); - CHECK(std::hash {}(json::array({1, 2, 3})) == 717272658337467U); - } + // number + hashes.insert(std::hash {}(json(int(0)))); + hashes.insert(std::hash {}(json(unsigned(0)))); - SECTION("object") - { - CHECK(std::hash {}(json::object()) == 2654435832U); - CHECK(std::hash {}(json::object({{"foo", "bar"}})) == 4042265434648078139U); - } + hashes.insert(std::hash {}(json(-1))); + hashes.insert(std::hash {}(json(0.0))); + hashes.insert(std::hash {}(json(42.23))); - SECTION("binary") - { - CHECK(std::hash {}(json::binary({})) == 11093832941624U); - CHECK(std::hash {}(json::binary({}, 0)) == 11093832941691U); - CHECK(std::hash {}(json::binary({}, 42)) == 11093832941581U); - CHECK(std::hash {}(json::binary({1, 2, 3})) == 3005324138949694928U); - CHECK(std::hash {}(json::binary({1, 2, 3}, 0)) == 3005324138988516582U); - CHECK(std::hash {}(json::binary({1, 2, 3}, 42)) == 3005324138986241627U); - } + // array + hashes.insert(std::hash {}(json::array())); + hashes.insert(std::hash {}(json::array({1, 2, 3}))); - SECTION("discarded") - { - CHECK(std::hash {}(json(json::value_t::discarded)) == 2654436338U); - } + // object + hashes.insert(std::hash {}(json::object())); + hashes.insert(std::hash {}(json::object({{"foo", "bar"}}))); + + // binary + hashes.insert(std::hash {}(json::binary({}))); + hashes.insert(std::hash {}(json::binary({}, 0))); + hashes.insert(std::hash {}(json::binary({}, 42))); + hashes.insert(std::hash {}(json::binary({1, 2, 3}))); + hashes.insert(std::hash {}(json::binary({1, 2, 3}, 0))); + hashes.insert(std::hash {}(json::binary({1, 2, 3}, 42))); + + // discarded + hashes.insert(std::hash {}(json(json::value_t::discarded))); + + CHECK(hashes.size() == 21); } \ No newline at end of file From b821ed074ff93e7108deb81cb5191ef13297b2eb Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 15 Jul 2020 13:45:16 +0200 Subject: [PATCH 5/5] :bulb: add documentation --- include/nlohmann/detail/hash.hpp | 35 +++++++++++++++++++++++--------- single_include/nlohmann/json.hpp | 35 +++++++++++++++++++++++--------- test/src/unit-hash.cpp | 4 ++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp index a4aaae4a3..d3313e968 100644 --- a/include/nlohmann/detail/hash.hpp +++ b/include/nlohmann/detail/hash.hpp @@ -1,6 +1,6 @@ #pragma once -#include // size_t +#include // size_t, uint8_t #include // hash namespace nlohmann @@ -8,12 +8,24 @@ namespace nlohmann namespace detail { +// boost::hash_combine std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; } +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ template std::size_t hash(const BasicJsonType& j) { @@ -22,15 +34,18 @@ std::size_t hash(const BasicJsonType& j) using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; + const auto type = static_cast(j.type()); switch (j.type()) { case BasicJsonType::value_t::null: case BasicJsonType::value_t::discarded: - return combine(static_cast(j.type()), 0); + { + return combine(type, 0); + } case BasicJsonType::value_t::object: { - auto seed = combine(static_cast(j.type()), j.size()); + auto seed = combine(type, j.size()); for (const auto& element : j.items()) { const auto h = std::hash {}(element.key()); @@ -42,7 +57,7 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::array: { - auto seed = combine(static_cast(j.type()), j.size()); + auto seed = combine(type, j.size()); for (const auto& element : j) { seed = combine(seed, hash(element)); @@ -53,36 +68,36 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::string: { const auto h = std::hash {}(j.template get_ref()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case BasicJsonType::value_t::boolean: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case BasicJsonType::value_t::number_integer: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::number_unsigned: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::number_float: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::binary: { - auto seed = combine(static_cast(j.type()), j.get_binary().size()); + auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f5a6d8fea..5e32f0b6b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4445,7 +4445,7 @@ class byte_container_with_subtype : public BinaryType // #include -#include // size_t +#include // size_t, uint8_t #include // hash namespace nlohmann @@ -4453,12 +4453,24 @@ namespace nlohmann namespace detail { +// boost::hash_combine std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; } +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ template std::size_t hash(const BasicJsonType& j) { @@ -4467,15 +4479,18 @@ std::size_t hash(const BasicJsonType& j) using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; + const auto type = static_cast(j.type()); switch (j.type()) { case BasicJsonType::value_t::null: case BasicJsonType::value_t::discarded: - return combine(static_cast(j.type()), 0); + { + return combine(type, 0); + } case BasicJsonType::value_t::object: { - auto seed = combine(static_cast(j.type()), j.size()); + auto seed = combine(type, j.size()); for (const auto& element : j.items()) { const auto h = std::hash {}(element.key()); @@ -4487,7 +4502,7 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::array: { - auto seed = combine(static_cast(j.type()), j.size()); + auto seed = combine(type, j.size()); for (const auto& element : j) { seed = combine(seed, hash(element)); @@ -4498,36 +4513,36 @@ std::size_t hash(const BasicJsonType& j) case BasicJsonType::value_t::string: { const auto h = std::hash {}(j.template get_ref()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case BasicJsonType::value_t::boolean: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case BasicJsonType::value_t::number_integer: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::number_unsigned: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::number_float: { const auto h = std::hash {}(j.template get()); - return combine(static_cast(j.type()), h); + return combine(type, h); } case nlohmann::detail::value_t::binary: { - auto seed = combine(static_cast(j.type()), j.get_binary().size()); + auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); diff --git a/test/src/unit-hash.cpp b/test/src/unit-hash.cpp index fddaceb98..186cad884 100644 --- a/test/src/unit-hash.cpp +++ b/test/src/unit-hash.cpp @@ -54,7 +54,7 @@ TEST_CASE("hash") hashes.insert(std::hash {}(json("foo"))); // number - hashes.insert(std::hash {}(json(int(0)))); + hashes.insert(std::hash {}(json(0))); hashes.insert(std::hash {}(json(unsigned(0)))); hashes.insert(std::hash {}(json(-1))); @@ -81,4 +81,4 @@ TEST_CASE("hash") hashes.insert(std::hash {}(json(json::value_t::discarded))); CHECK(hashes.size() == 21); -} \ No newline at end of file +}