From 5b229f4cce957136dfd5fd34beeb9ef0336ae958 Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
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 00000000..b0be7fb2
--- /dev/null
+++ b/include/nlohmann/detail/hash.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <functional> // 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<typename BasicJsonType>
+std::size_t hash(const BasicJsonType& j)
+{
+    switch (j.type())
+    {
+        case BasicJsonType::value_t::null:
+        case BasicJsonType::discarded:
+            return combine(static_cast<std::size_t>(j.type()), 0);
+
+        case BasicJsonType::value_t::object:
+        {
+            auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
+            for (const auto& element : j.items())
+            {
+                const auto h = std::hash<typename BasicJsonType::string_t> {}(element.key());
+                seed = combine(seed, h);
+                seed = combine(seed, hash(element.value()));
+            }
+            return seed;
+        }
+
+        case BasicJsonType::value_t::array:
+        {
+            auto seed = combine(static_cast<std::size_t>(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<typename BasicJsonType::string_t> {}(j.template get_ref<const typename BasicJsonType::string_t&>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case BasicJsonType::value_t::boolean:
+        {
+            const auto h = std::hash<bool> {}(j.template get<bool>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case BasicJsonType::value_t::number_integer:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_integer_t> {}(j.template get<typename BasicJsonType::number_integer_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::number_unsigned:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_unsigned_t> {}(j.template get<typename BasicJsonType::number_unsigned_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::number_float:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_float_t> {}(j.template get<typename BasicJsonType::number_float_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::binary:
+        {
+            auto seed = combine(static_cast<std::size_t>(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<std::uint8_t> {}(byte));
+            }
+            return seed;
+        }
+    }
+
+    return 0;
+}
+
+}  // namespace detail
+}  // namespace nlohmann
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 7c2cf2b1..1a45b776 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -51,6 +51,7 @@ SOFTWARE.
 #include <nlohmann/detail/conversions/from_json.hpp>
 #include <nlohmann/detail/conversions/to_json.hpp>
 #include <nlohmann/detail/exceptions.hpp>
+#include <nlohmann/detail/hash.hpp>
 #include <nlohmann/detail/input/binary_reader.hpp>
 #include <nlohmann/detail/input/input_adapters.hpp>
 #include <nlohmann/detail/input/lexer.hpp>
@@ -8698,9 +8699,7 @@ struct hash<nlohmann::json>
     */
     std::size_t operator()(const nlohmann::json& j) const
     {
-        // a naive hashing via the string representation
-        const auto& h = hash<nlohmann::json::string_t>();
-        return h(j.dump());
+        return nlohmann::detail::hash(j);
     }
 };
 
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 82c0d484..9a9b3a2f 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 <nlohmann/detail/exceptions.hpp>
 
+// #include <nlohmann/detail/hash.hpp>
+
+
+#include <functional> // 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<typename BasicJsonType>
+std::size_t hash(const BasicJsonType& j)
+{
+    switch (j.type())
+    {
+        case BasicJsonType::value_t::null:
+        case BasicJsonType::discarded:
+            return combine(static_cast<std::size_t>(j.type()), 0);
+
+        case BasicJsonType::value_t::object:
+        {
+            auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
+            for (const auto& element : j.items())
+            {
+                const auto h = std::hash<typename BasicJsonType::string_t> {}(element.key());
+                seed = combine(seed, h);
+                seed = combine(seed, hash(element.value()));
+            }
+            return seed;
+        }
+
+        case BasicJsonType::value_t::array:
+        {
+            auto seed = combine(static_cast<std::size_t>(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<typename BasicJsonType::string_t> {}(j.template get_ref<const typename BasicJsonType::string_t&>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case BasicJsonType::value_t::boolean:
+        {
+            const auto h = std::hash<bool> {}(j.template get<bool>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case BasicJsonType::value_t::number_integer:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_integer_t> {}(j.template get<typename BasicJsonType::number_integer_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::number_unsigned:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_unsigned_t> {}(j.template get<typename BasicJsonType::number_unsigned_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::number_float:
+        {
+            const auto h = std::hash<typename BasicJsonType::number_float_t> {}(j.template get<typename BasicJsonType::number_float_t>());
+            return combine(static_cast<std::size_t>(j.type()), h);
+        }
+
+        case nlohmann::detail::value_t::binary:
+        {
+            auto seed = combine(static_cast<std::size_t>(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<std::uint8_t> {}(byte));
+            }
+            return seed;
+        }
+    }
+
+    return 0;
+}
+
+}  // namespace detail
+}  // namespace nlohmann
+
 // #include <nlohmann/detail/input/binary_reader.hpp>
 
 
@@ -24686,9 +24781,7 @@ struct hash<nlohmann::json>
     */
     std::size_t operator()(const nlohmann::json& j) const
     {
-        // a naive hashing via the string representation
-        const auto& h = hash<nlohmann::json::string_t>();
-        return h(j.dump());
+        return nlohmann::detail::hash(j);
     }
 };
 

From 33b0bed7fe6e7e64aca0b0ba5c49001b0123cb7d Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
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 b0be7fb2..404a2bd3 100644
--- a/include/nlohmann/detail/hash.hpp
+++ b/include/nlohmann/detail/hash.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstddef> // size_t
 #include <functional> // 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<std::size_t>(j.type()), 0);
 
         case BasicJsonType::value_t::object:
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 9a9b3a2f..6a7a9ff4 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 <nlohmann/detail/hash.hpp>
 
 
+#include <cstddef> // size_t
 #include <functional> // 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<std::size_t>(j.type()), 0);
 
         case BasicJsonType::value_t::object:

From 9449dfcc6a48493877c81e06ae6116ac05f76385 Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
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 404a2bd3..a4aaae4a 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<typename BasicJsonType>
 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<std::size_t>(j.type()), j.size());
             for (const auto& element : j.items())
             {
-                const auto h = std::hash<typename BasicJsonType::string_t> {}(element.key());
+                const auto h = std::hash<string_t> {}(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<typename BasicJsonType::string_t> {}(j.template get_ref<const typename BasicJsonType::string_t&>());
+            const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>());
             return combine(static_cast<std::size_t>(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<typename BasicJsonType::number_integer_t> {}(j.template get<typename BasicJsonType::number_integer_t>());
+            const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::number_unsigned:
         {
-            const auto h = std::hash<typename BasicJsonType::number_unsigned_t> {}(j.template get<typename BasicJsonType::number_unsigned_t>());
+            const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::number_float:
         {
-            const auto h = std::hash<typename BasicJsonType::number_float_t> {}(j.template get<typename BasicJsonType::number_float_t>());
+            const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::binary:
         {
             auto seed = combine(static_cast<std::size_t>(j.type()), j.get_binary().size());
+            const auto h = std::hash<bool> {}(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 6a7a9ff4..f5a6d8fe 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<typename BasicJsonType>
 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<std::size_t>(j.type()), j.size());
             for (const auto& element : j.items())
             {
-                const auto h = std::hash<typename BasicJsonType::string_t> {}(element.key());
+                const auto h = std::hash<string_t> {}(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<typename BasicJsonType::string_t> {}(j.template get_ref<const typename BasicJsonType::string_t&>());
+            const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>());
             return combine(static_cast<std::size_t>(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<typename BasicJsonType::number_integer_t> {}(j.template get<typename BasicJsonType::number_integer_t>());
+            const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::number_unsigned:
         {
-            const auto h = std::hash<typename BasicJsonType::number_unsigned_t> {}(j.template get<typename BasicJsonType::number_unsigned_t>());
+            const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::number_float:
         {
-            const auto h = std::hash<typename BasicJsonType::number_float_t> {}(j.template get<typename BasicJsonType::number_float_t>());
+            const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
             return combine(static_cast<std::size_t>(j.type()), h);
         }
 
         case nlohmann::detail::value_t::binary:
         {
             auto seed = combine(static_cast<std::size_t>(j.type()), j.get_binary().size());
+            const auto h = std::hash<bool> {}(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 9146d43b..91ff5a21 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 bcf8de7d..b01fa872 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 00000000..f80a6596
--- /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 <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
+Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
+
+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 <nlohmann/json.hpp>
+using json = nlohmann::json;
+
+TEST_CASE("hash")
+{
+    SECTION("null")
+    {
+        CHECK(std::hash<json> {}(json(nullptr)) == 2654435769U);
+    }
+
+    SECTION("boolean")
+    {
+        CHECK(std::hash<json> {}(json(true)) == 2654436031U);
+        CHECK(std::hash<json> {}(json(false)) == 2654436030U);
+    }
+
+    SECTION("string")
+    {
+        CHECK(std::hash<json> {}(json("")) == 11160318156688833227U);
+        CHECK(std::hash<json> {}(json("foo")) == 910203211069189493U);
+    }
+
+    SECTION("number")
+    {
+        CHECK(std::hash<json> {}(json(int(0))) == 2654436095U);
+        CHECK(std::hash<json> {}(json(unsigned(0))) == 2654436156U);
+
+        CHECK(std::hash<json> {}(json(-1)) == 2654436092U);
+        CHECK(std::hash<json> {}(json(0.0)) == 2654436221U);
+        CHECK(std::hash<json> {}(json(42.23)) == 4631140164097181104U);
+    }
+
+    SECTION("array")
+    {
+        CHECK(std::hash<json> {}(json::array()) == 2654435899U);
+        CHECK(std::hash<json> {}(json::array({1, 2, 3})) == 717272658337467U);
+    }
+
+    SECTION("object")
+    {
+        CHECK(std::hash<json> {}(json::object()) == 2654435832U);
+        CHECK(std::hash<json> {}(json::object({{"foo", "bar"}})) == 4042265434648078139U);
+    }
+
+    SECTION("binary")
+    {
+        CHECK(std::hash<json> {}(json::binary({})) == 11093832941624U);
+        CHECK(std::hash<json> {}(json::binary({}, 0)) == 11093832941691U);
+        CHECK(std::hash<json> {}(json::binary({}, 42)) == 11093832941581U);
+        CHECK(std::hash<json> {}(json::binary({1, 2, 3})) == 3005324138949694928U);
+        CHECK(std::hash<json> {}(json::binary({1, 2, 3}, 0)) == 3005324138988516582U);
+        CHECK(std::hash<json> {}(json::binary({1, 2, 3}, 42)) == 3005324138986241627U);
+    }
+
+    SECTION("discarded")
+    {
+        CHECK(std::hash<json> {}(json(json::value_t::discarded)) == 2654436338U);
+    }
+}
\ No newline at end of file

From 496ddd127c95b8e424408b8299e51ea04675e17e Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
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 f80a6596..fddaceb9 100644
--- a/test/src/unit-hash.cpp
+++ b/test/src/unit-hash.cpp
@@ -32,59 +32,53 @@ SOFTWARE.
 #include <nlohmann/json.hpp>
 using json = nlohmann::json;
 
+#include <set>
+
 TEST_CASE("hash")
 {
-    SECTION("null")
-    {
-        CHECK(std::hash<json> {}(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> {}(json(true)) == 2654436031U);
-        CHECK(std::hash<json> {}(json(false)) == 2654436030U);
-    }
+    std::set<std::size_t> hashes;
 
-    SECTION("string")
-    {
-        CHECK(std::hash<json> {}(json("")) == 11160318156688833227U);
-        CHECK(std::hash<json> {}(json("foo")) == 910203211069189493U);
-    }
+    // null
+    hashes.insert(std::hash<json> {}(json(nullptr)));
 
-    SECTION("number")
-    {
-        CHECK(std::hash<json> {}(json(int(0))) == 2654436095U);
-        CHECK(std::hash<json> {}(json(unsigned(0))) == 2654436156U);
+    // boolean
+    hashes.insert(std::hash<json> {}(json(true)));
+    hashes.insert(std::hash<json> {}(json(false)));
 
-        CHECK(std::hash<json> {}(json(-1)) == 2654436092U);
-        CHECK(std::hash<json> {}(json(0.0)) == 2654436221U);
-        CHECK(std::hash<json> {}(json(42.23)) == 4631140164097181104U);
-    }
+    // string
+    hashes.insert(std::hash<json> {}(json("")));
+    hashes.insert(std::hash<json> {}(json("foo")));
 
-    SECTION("array")
-    {
-        CHECK(std::hash<json> {}(json::array()) == 2654435899U);
-        CHECK(std::hash<json> {}(json::array({1, 2, 3})) == 717272658337467U);
-    }
+    // number
+    hashes.insert(std::hash<json> {}(json(int(0))));
+    hashes.insert(std::hash<json> {}(json(unsigned(0))));
 
-    SECTION("object")
-    {
-        CHECK(std::hash<json> {}(json::object()) == 2654435832U);
-        CHECK(std::hash<json> {}(json::object({{"foo", "bar"}})) == 4042265434648078139U);
-    }
+    hashes.insert(std::hash<json> {}(json(-1)));
+    hashes.insert(std::hash<json> {}(json(0.0)));
+    hashes.insert(std::hash<json> {}(json(42.23)));
 
-    SECTION("binary")
-    {
-        CHECK(std::hash<json> {}(json::binary({})) == 11093832941624U);
-        CHECK(std::hash<json> {}(json::binary({}, 0)) == 11093832941691U);
-        CHECK(std::hash<json> {}(json::binary({}, 42)) == 11093832941581U);
-        CHECK(std::hash<json> {}(json::binary({1, 2, 3})) == 3005324138949694928U);
-        CHECK(std::hash<json> {}(json::binary({1, 2, 3}, 0)) == 3005324138988516582U);
-        CHECK(std::hash<json> {}(json::binary({1, 2, 3}, 42)) == 3005324138986241627U);
-    }
+    // array
+    hashes.insert(std::hash<json> {}(json::array()));
+    hashes.insert(std::hash<json> {}(json::array({1, 2, 3})));
 
-    SECTION("discarded")
-    {
-        CHECK(std::hash<json> {}(json(json::value_t::discarded)) == 2654436338U);
-    }
+    // object
+    hashes.insert(std::hash<json> {}(json::object()));
+    hashes.insert(std::hash<json> {}(json::object({{"foo", "bar"}})));
+
+    // binary
+    hashes.insert(std::hash<json> {}(json::binary({})));
+    hashes.insert(std::hash<json> {}(json::binary({}, 0)));
+    hashes.insert(std::hash<json> {}(json::binary({}, 42)));
+    hashes.insert(std::hash<json> {}(json::binary({1, 2, 3})));
+    hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 0)));
+    hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 42)));
+
+    // discarded
+    hashes.insert(std::hash<json> {}(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 <mail@nlohmann.me>
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 a4aaae4a..d3313e96 100644
--- a/include/nlohmann/detail/hash.hpp
+++ b/include/nlohmann/detail/hash.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <cstddef> // size_t
+#include <cstddef> // size_t, uint8_t
 #include <functional> // 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<typename BasicJsonType>
 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<std::size_t>(j.type());
     switch (j.type())
     {
         case BasicJsonType::value_t::null:
         case BasicJsonType::value_t::discarded:
-            return combine(static_cast<std::size_t>(j.type()), 0);
+        {
+            return combine(type, 0);
+        }
 
         case BasicJsonType::value_t::object:
         {
-            auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
+            auto seed = combine(type, j.size());
             for (const auto& element : j.items())
             {
                 const auto h = std::hash<string_t> {}(element.key());
@@ -42,7 +57,7 @@ std::size_t hash(const BasicJsonType& j)
 
         case BasicJsonType::value_t::array:
         {
-            auto seed = combine(static_cast<std::size_t>(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<string_t> {}(j.template get_ref<const string_t&>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case BasicJsonType::value_t::boolean:
         {
             const auto h = std::hash<bool> {}(j.template get<bool>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case BasicJsonType::value_t::number_integer:
         {
             const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::number_unsigned:
         {
             const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::number_float:
         {
             const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::binary:
         {
-            auto seed = combine(static_cast<std::size_t>(j.type()), j.get_binary().size());
+            auto seed = combine(type, j.get_binary().size());
             const auto h = std::hash<bool> {}(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 f5a6d8fe..5e32f0b6 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 <nlohmann/detail/hash.hpp>
 
 
-#include <cstddef> // size_t
+#include <cstddef> // size_t, uint8_t
 #include <functional> // 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<typename BasicJsonType>
 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<std::size_t>(j.type());
     switch (j.type())
     {
         case BasicJsonType::value_t::null:
         case BasicJsonType::value_t::discarded:
-            return combine(static_cast<std::size_t>(j.type()), 0);
+        {
+            return combine(type, 0);
+        }
 
         case BasicJsonType::value_t::object:
         {
-            auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
+            auto seed = combine(type, j.size());
             for (const auto& element : j.items())
             {
                 const auto h = std::hash<string_t> {}(element.key());
@@ -4487,7 +4502,7 @@ std::size_t hash(const BasicJsonType& j)
 
         case BasicJsonType::value_t::array:
         {
-            auto seed = combine(static_cast<std::size_t>(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<string_t> {}(j.template get_ref<const string_t&>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case BasicJsonType::value_t::boolean:
         {
             const auto h = std::hash<bool> {}(j.template get<bool>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case BasicJsonType::value_t::number_integer:
         {
             const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::number_unsigned:
         {
             const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::number_float:
         {
             const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
-            return combine(static_cast<std::size_t>(j.type()), h);
+            return combine(type, h);
         }
 
         case nlohmann::detail::value_t::binary:
         {
-            auto seed = combine(static_cast<std::size_t>(j.type()), j.get_binary().size());
+            auto seed = combine(type, j.get_binary().size());
             const auto h = std::hash<bool> {}(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 fddaceb9..186cad88 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> {}(json("foo")));
 
     // number
-    hashes.insert(std::hash<json> {}(json(int(0))));
+    hashes.insert(std::hash<json> {}(json(0)));
     hashes.insert(std::hash<json> {}(json(unsigned(0))));
 
     hashes.insert(std::hash<json> {}(json(-1)));
@@ -81,4 +81,4 @@ TEST_CASE("hash")
     hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
 
     CHECK(hashes.size() == 21);
-}
\ No newline at end of file
+}