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);
     }
 };