diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp
index 3aad6648..87f2119f 100644
--- a/include/nlohmann/detail/input/json_sax.hpp
+++ b/include/nlohmann/detail/input/json_sax.hpp
@@ -20,6 +20,9 @@ struct json_sax
     /// type for floating-point numbers
     using number_float_t = typename BasicJsonType::number_float_t;
 
+    /// constant to indicate that no size limit is given for array or object
+    static constexpr auto no_limit = std::size_t(-1);
+
     /*!
     @brief a null value was read
     @return whether parsing should proceed
@@ -60,22 +63,22 @@ struct json_sax
     @param[in] val  string value
     @return whether parsing should proceed
     */
-    virtual bool string(const std::string& val) = 0;
+    virtual bool string(std::string&& val) = 0;
 
     /*!
     @brief the beginning of an object was read
-    @param[in] elements  number of object elements or -1 if unknown
+    @param[in] elements  number of object elements or no_limit if unknown
     @return whether parsing should proceed
     @note binary formats may report the number of elements
     */
-    virtual bool start_object(std::size_t elements) = 0;
+    virtual bool start_object(std::size_t elements = no_limit) = 0;
 
     /*!
     @brief an object key was read
     @param[in] val  object key
     @return whether parsing should proceed
     */
-    virtual bool key(const std::string& val) = 0;
+    virtual bool key(std::string&& val) = 0;
 
     /*!
     @brief the end of an object was read
@@ -85,11 +88,11 @@ struct json_sax
 
     /*!
     @brief the beginning of an array was read
-    @param[in] elements  number of array elements or -1 if unknown
+    @param[in] elements  number of array elements or no_limit if unknown
     @return whether parsing should proceed
     @note binary formats may report the number of elements
     */
-    virtual bool start_array(std::size_t elements) = 0;
+    virtual bool start_array(std::size_t elements = no_limit) = 0;
 
     /*!
     @brief the end of an array was read
diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp
index 75001652..ea116093 100644
--- a/include/nlohmann/detail/input/lexer.hpp
+++ b/include/nlohmann/detail/input/lexer.hpp
@@ -1130,7 +1130,7 @@ scan_number_done:
     }
 
     /// return current string value (implicitly resets the token; useful only once)
-    std::string move_string()
+    std::string&& move_string()
     {
         return std::move(token_buffer);
     }
diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp
index aa84a2be..8fc29981 100644
--- a/include/nlohmann/detail/input/parser.hpp
+++ b/include/nlohmann/detail/input/parser.hpp
@@ -53,7 +53,7 @@ class parser
         value
     };
 
-    using json_sax = json_sax<BasicJsonType>;
+    using json_sax_t = json_sax<BasicJsonType>;
 
     using parser_callback_t =
         std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
@@ -65,7 +65,7 @@ class parser
         : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_)
     {}
 
-    parser(detail::input_adapter_t adapter, json_sax* s)
+    parser(detail::input_adapter_t adapter, json_sax_t* s)
         : m_lexer(adapter), sax(s)
     {}
 
@@ -541,7 +541,7 @@ class parser
         {
             case token_type::begin_object:
             {
-                if (not sax->start_object(std::size_t(-1)))
+                if (not sax->start_object())
                 {
                     return false;
                 }
@@ -610,7 +610,7 @@ class parser
 
             case token_type::begin_array:
             {
-                if (not sax->start_array(std::size_t(-1)))
+                if (not sax->start_array())
                 {
                     return false;
                 }
@@ -772,7 +772,7 @@ class parser
     /// whether to throw exceptions in case of errors
     const bool allow_exceptions = true;
     /// associated SAX parse event receiver
-    json_sax* sax = nullptr;
+    json_sax_t* sax = nullptr;
 };
 }
 }
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 8f5aee0c..f1b7e5dd 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -1105,7 +1105,7 @@ class basic_json
     */
     using parser_callback_t = typename parser::parser_callback_t;
 
-    using json_sax = typename parser::json_sax;
+    using json_sax_t = typename parser::json_sax_t;
 
     //////////////////
     // constructors //
@@ -5926,12 +5926,12 @@ class basic_json
         return parser(i).accept(true);
     }
 
-    static bool sax_parse(detail::input_adapter i, json_sax* sax)
+    static bool sax_parse(detail::input_adapter i, json_sax_t* sax)
     {
         return parser(i, sax).sax_parse();
     }
 
-    static bool sax_parse(detail::input_adapter& i, json_sax* sax)
+    static bool sax_parse(detail::input_adapter& i, json_sax_t* sax)
     {
         return parser(i, sax).sax_parse();
     }
@@ -6009,7 +6009,7 @@ class basic_json
                  std::is_base_of<
                      std::random_access_iterator_tag,
                      typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
-    static bool sax_parse(IteratorType first, IteratorType last, json_sax* sax)
+    static bool sax_parse(IteratorType first, IteratorType last, json_sax_t* sax)
     {
         return parser(detail::input_adapter(first, last), sax).sax_parse();
     }
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 01e77f41..92123591 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -2969,7 +2969,7 @@ scan_number_done:
     }
 
     /// return current string value (implicitly resets the token; useful only once)
-    std::string move_string()
+    std::string&& move_string()
     {
         return std::move(token_buffer);
     }
@@ -3154,6 +3154,9 @@ struct json_sax
     /// type for floating-point numbers
     using number_float_t = typename BasicJsonType::number_float_t;
 
+    /// constant to indicate that no size limit is given for array or object
+    static constexpr auto no_limit = std::size_t(-1);
+
     /*!
     @brief a null value was read
     @return whether parsing should proceed
@@ -3194,22 +3197,22 @@ struct json_sax
     @param[in] val  string value
     @return whether parsing should proceed
     */
-    virtual bool string(const std::string& val) = 0;
+    virtual bool string(std::string&& val) = 0;
 
     /*!
     @brief the beginning of an object was read
-    @param[in] elements  number of object elements or -1 if unknown
+    @param[in] elements  number of object elements or no_limit if unknown
     @return whether parsing should proceed
     @note binary formats may report the number of elements
     */
-    virtual bool start_object(std::size_t elements) = 0;
+    virtual bool start_object(std::size_t elements = no_limit) = 0;
 
     /*!
     @brief an object key was read
     @param[in] val  object key
     @return whether parsing should proceed
     */
-    virtual bool key(const std::string& val) = 0;
+    virtual bool key(std::string&& val) = 0;
 
     /*!
     @brief the end of an object was read
@@ -3219,11 +3222,11 @@ struct json_sax
 
     /*!
     @brief the beginning of an array was read
-    @param[in] elements  number of array elements or -1 if unknown
+    @param[in] elements  number of array elements or no_limit if unknown
     @return whether parsing should proceed
     @note binary formats may report the number of elements
     */
-    virtual bool start_array(std::size_t elements) = 0;
+    virtual bool start_array(std::size_t elements = no_limit) = 0;
 
     /*!
     @brief the end of an array was read
@@ -3297,7 +3300,7 @@ class parser
         value
     };
 
-    using json_sax = json_sax<BasicJsonType>;
+    using json_sax_t = json_sax<BasicJsonType>;
 
     using parser_callback_t =
         std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
@@ -3309,7 +3312,7 @@ class parser
         : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_)
     {}
 
-    parser(detail::input_adapter_t adapter, json_sax* s)
+    parser(detail::input_adapter_t adapter, json_sax_t* s)
         : m_lexer(adapter), sax(s)
     {}
 
@@ -3785,7 +3788,7 @@ class parser
         {
             case token_type::begin_object:
             {
-                if (not sax->start_object(std::size_t(-1)))
+                if (not sax->start_object())
                 {
                     return false;
                 }
@@ -3854,7 +3857,7 @@ class parser
 
             case token_type::begin_array:
             {
-                if (not sax->start_array(std::size_t(-1)))
+                if (not sax->start_array())
                 {
                     return false;
                 }
@@ -4016,7 +4019,7 @@ class parser
     /// whether to throw exceptions in case of errors
     const bool allow_exceptions = true;
     /// associated SAX parse event receiver
-    json_sax* sax = nullptr;
+    json_sax_t* sax = nullptr;
 };
 }
 }
@@ -11013,7 +11016,7 @@ class basic_json
     */
     using parser_callback_t = typename parser::parser_callback_t;
 
-    using json_sax = typename parser::json_sax;
+    using json_sax_t = typename parser::json_sax_t;
 
     //////////////////
     // constructors //
@@ -15834,12 +15837,12 @@ class basic_json
         return parser(i).accept(true);
     }
 
-    static bool sax_parse(detail::input_adapter i, json_sax* sax)
+    static bool sax_parse(detail::input_adapter i, json_sax_t* sax)
     {
         return parser(i, sax).sax_parse();
     }
 
-    static bool sax_parse(detail::input_adapter& i, json_sax* sax)
+    static bool sax_parse(detail::input_adapter& i, json_sax_t* sax)
     {
         return parser(i, sax).sax_parse();
     }
@@ -15917,7 +15920,7 @@ class basic_json
                  std::is_base_of<
                      std::random_access_iterator_tag,
                      typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
-    static bool sax_parse(IteratorType first, IteratorType last, json_sax* sax)
+    static bool sax_parse(IteratorType first, IteratorType last, json_sax_t* sax)
     {
         return parser(detail::input_adapter(first, last), sax).sax_parse();
     }
diff --git a/test/src/unit-class_parser.cpp b/test/src/unit-class_parser.cpp
index cfa90f26..bbf10589 100644
--- a/test/src/unit-class_parser.cpp
+++ b/test/src/unit-class_parser.cpp
@@ -34,7 +34,7 @@ using nlohmann::json;
 
 #include <valarray>
 
-class SaxEventLogger : public nlohmann::json::json_sax
+class SaxEventLogger : public nlohmann::json::json_sax_t
 {
   public:
     bool null() override
@@ -67,7 +67,7 @@ class SaxEventLogger : public nlohmann::json::json_sax
         return true;
     }
 
-    bool string(const std::string& val) override
+    bool string(std::string&& val) override
     {
         events.push_back("string(" + val + ")");
         return true;
@@ -75,7 +75,7 @@ class SaxEventLogger : public nlohmann::json::json_sax
 
     bool start_object(std::size_t elements) override
     {
-        if (elements == std::size_t(-1))
+        if (elements == no_limit)
         {
             events.push_back("start_object()");
         }
@@ -86,13 +86,13 @@ class SaxEventLogger : public nlohmann::json::json_sax
         return true;
     }
 
-    bool key(const std::string& val) override
+    bool key(std::string&& val) override
     {
         events.push_back("key(" + val + ")");
         return true;
     }
 
-    bool end_object()override
+    bool end_object() override
     {
         events.push_back("end_object()");
         return true;
@@ -100,7 +100,7 @@ class SaxEventLogger : public nlohmann::json::json_sax
 
     bool start_array(std::size_t elements) override
     {
-        if (elements == std::size_t(-1))
+        if (elements == no_limit)
         {
             events.push_back("start_array()");
         }
@@ -134,6 +134,129 @@ class SaxEventLogger : public nlohmann::json::json_sax
     bool errored = false;
 };
 
+class SaxDomParser : public nlohmann::json::json_sax_t
+{
+  public:
+    bool null() override
+    {
+        handle_value(nullptr);
+        return true;
+    }
+
+    bool boolean(bool val) override
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_integer(json::number_integer_t val) override
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_unsigned(json::number_unsigned_t val) override
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_float(json::number_float_t val, const std::string&) override
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool string(std::string&& val) override
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool start_object(std::size_t) override
+    {
+        ref_stack.push_back(handle_value(json::value_t::object));
+        return true;
+    }
+
+    bool key(std::string&& val) override
+    {
+        last_key = val;
+        return true;
+    }
+
+    bool end_object() override
+    {
+        ref_stack.pop_back();
+        return true;
+    }
+
+    bool start_array(std::size_t) override
+    {
+        ref_stack.push_back(handle_value(json::value_t::array));
+        return true;
+    }
+
+    bool end_array() override
+    {
+        ref_stack.pop_back();
+        return true;
+    }
+
+    bool binary(const std::vector<uint8_t>&) override
+    {
+        return true;
+    }
+
+    bool parse_error(std::size_t position, const std::string&) override
+    {
+        return false;
+    }
+
+    json& get_value()
+    {
+        return root;
+    }
+
+  private:
+    /// the parsed JSON value
+    json root;
+    /// stack to model hierarchy of values
+    std::vector<json*> ref_stack;
+    /// helper variable for object keys
+    std::string last_key;
+
+    /*!
+    @invariant If the ref stack is empty, then the passed value will be the new
+               root.
+    @invariant If the ref stack contains a value, then it is an array or an
+               object to which we can add elements
+    */
+    json* handle_value(json&& j)
+    {
+        if (ref_stack.empty())
+        {
+            assert(root.is_null());
+            root = j;
+            return &root;
+        }
+        else
+        {
+            assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+            if (ref_stack.back()->is_array())
+            {
+                ref_stack.back()->push_back(j);
+                return &(ref_stack.back()->back());
+            }
+            else
+            {
+                json& r = ref_stack.back()->operator[](last_key) = j;
+                return &r;
+            }
+        }
+    }
+};
+
 json parser_helper(const std::string& s);
 bool accept_helper(const std::string& s);
 
@@ -148,6 +271,10 @@ json parser_helper(const std::string& s)
     CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow));
     CHECK(j_nothrow == j);
 
+    SaxDomParser sdp;
+    json::sax_parse(s, &sdp);
+    CHECK(sdp.get_value() == j);
+
     return j;
 }
 
diff --git a/test/src/unit-deserialization.cpp b/test/src/unit-deserialization.cpp
index e48d6348..d50e6924 100644
--- a/test/src/unit-deserialization.cpp
+++ b/test/src/unit-deserialization.cpp
@@ -34,7 +34,7 @@ using nlohmann::json;
 #include <iostream>
 #include <valarray>
 
-struct SaxEventLogger : public nlohmann::json::json_sax
+struct SaxEventLogger : public nlohmann::json::json_sax_t
 {
     bool null() override
     {
@@ -66,7 +66,7 @@ struct SaxEventLogger : public nlohmann::json::json_sax
         return true;
     }
 
-    bool string(const std::string& val) override
+    bool string(std::string&& val) override
     {
         events.push_back("string(" + val + ")");
         return true;
@@ -85,7 +85,7 @@ struct SaxEventLogger : public nlohmann::json::json_sax
         return true;
     }
 
-    bool key(const std::string& val) override
+    bool key(std::string&& val) override
     {
         events.push_back("key(" + val + ")");
         return true;
@@ -135,7 +135,7 @@ struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
 {
     bool start_object(std::size_t elements) override
     {
-        if (elements == std::size_t(-1))
+        if (elements == no_limit)
         {
             events.push_back("start_object()");
         }
@@ -149,7 +149,7 @@ struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
 
 struct SaxEventLoggerExitAfterKey : public SaxEventLogger
 {
-    bool key(const std::string& val) override
+    bool key(std::string&& val) override
     {
         events.push_back("key(" + val + ")");
         return false;
@@ -160,7 +160,7 @@ struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
 {
     bool start_array(std::size_t elements) override
     {
-        if (elements == std::size_t(-1))
+        if (elements == no_limit)
         {
             events.push_back("start_array()");
         }