diff --git a/src/json.hpp b/src/json.hpp
index 3c615451..2df26ae0 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -748,8 +748,7 @@ struct is_basic_json_nested_type
     static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or
                                   std::is_same<T, typename BasicJsonType::const_iterator>::value or
                                   std::is_same<T, typename BasicJsonType::reverse_iterator>::value or
-                                  std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value or
-                                  std::is_same<T, typename BasicJsonType::json_pointer>::value;
+                                  std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value;
 };
 
 template<class BasicJsonType, class CompatibleArrayType>
@@ -1320,6 +1319,335 @@ struct adl_serializer
     }
 };
 
+/*!
+@brief JSON Pointer
+
+A JSON pointer defines a string syntax for identifying a specific value
+within a JSON document. It can be used with functions `at` and
+`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
+@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+
+@since version 2.0.0
+*/
+class json_pointer
+{
+    /// allow basic_json to access private members
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    friend class basic_json;
+
+  public:
+    /*!
+      @brief create JSON pointer
+
+      Create a JSON pointer according to the syntax described in
+      [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
+
+      @param[in] s  string representing the JSON pointer; if omitted, the
+      empty string is assumed which references the whole JSON
+      value
+
+      @throw parse_error.107 if the given JSON pointer @a s is nonempty and
+      does not begin with a slash (`/`); see example below
+
+      @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s
+      is not followed by `0` (representing `~`) or `1` (representing `/`);
+      see example below
+
+      @liveexample{The example shows the construction several valid JSON
+      pointers as well as the exceptional behavior.,json_pointer}
+
+      @since version 2.0.0
+      */
+    explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {}
+
+    /*!
+      @brief return a string representation of the JSON pointer
+
+      @invariant For each JSON pointer `ptr`, it holds:
+      @code {.cpp}
+      ptr == json_pointer(ptr.to_string());
+      @endcode
+
+      @return a string representation of the JSON pointer
+
+      @liveexample{The example shows the result of `to_string`.,
+      json_pointer__to_string}
+
+      @since version 2.0.0
+      */
+    std::string to_string() const noexcept
+    {
+        return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
+                               std::string{},
+                               [](const std::string & a, const std::string & b)
+        {
+            return a + "/" + escape(b);
+        });
+    }
+
+    /// @copydoc to_string()
+    operator std::string() const
+    {
+        return to_string();
+    }
+
+  private:
+    /*!
+      @brief remove and return last reference pointer
+      @throw out_of_range.405 if JSON pointer has no parent
+      */
+    std::string pop_back()
+    {
+        if (is_root())
+        {
+            JSON_THROW(
+                detail::out_of_range::create(405, "JSON pointer has no parent"));
+        }
+
+        auto last = reference_tokens.back();
+        reference_tokens.pop_back();
+        return last;
+    }
+
+    /// return whether pointer points to the root document
+    bool is_root() const
+    {
+        return reference_tokens.empty();
+    }
+
+    json_pointer top() const
+    {
+        if (is_root())
+        {
+            JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
+        }
+
+        json_pointer result = *this;
+        result.reference_tokens = {reference_tokens[0]};
+        return result;
+    }
+
+
+    /*!
+      @brief create and return a reference to the pointed to value
+
+      @complexity Linear in the number of reference tokens.
+
+      @throw parse_error.109 if array index is not a number
+      @throw type_error.313 if value cannot be unflattened
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const;
+
+    /*!
+      @brief return a reference to the pointed to value
+
+      @note This version does not throw if a value is not present, but tries
+      to create nested values instead. For instance, calling this function
+      with pointer `"/this/that"` on a null value is equivalent to calling
+      `operator[]("this").operator[]("that")` on that value, effectively
+      changing the null value to an object.
+
+      @param[in] ptr  a JSON value
+
+      @return reference to the JSON value pointed to by the JSON pointer
+
+      @complexity Linear in the length of the JSON pointer.
+
+      @throw parse_error.106   if an array index begins with '0'
+      @throw parse_error.109   if an array index was not a number
+      @throw out_of_range.404  if the JSON pointer can not be resolved
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const;
+
+    /*!
+      @throw parse_error.106   if an array index begins with '0'
+      @throw parse_error.109   if an array index was not a number
+      @throw out_of_range.402  if the array index '-' is used
+      @throw out_of_range.404  if the JSON pointer can not be resolved
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const;
+
+    /*!
+      @brief return a const reference to the pointed to value
+
+      @param[in] ptr  a JSON value
+
+      @return const reference to the JSON value pointed to by the JSON
+      pointer
+
+      @throw parse_error.106   if an array index begins with '0'
+      @throw parse_error.109   if an array index was not a number
+      @throw out_of_range.402  if the array index '-' is used
+      @throw out_of_range.404  if the JSON pointer can not be resolved
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const;
+
+    /*!
+      @throw parse_error.106   if an array index begins with '0'
+      @throw parse_error.109   if an array index was not a number
+      @throw out_of_range.402  if the array index '-' is used
+      @throw out_of_range.404  if the JSON pointer can not be resolved
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const;
+
+    /*!
+      @brief split the string input to reference tokens
+
+      @note This function is only called by the json_pointer constructor.
+      All exceptions below are documented there.
+
+      @throw parse_error.107  if the pointer is not empty or begins with '/'
+      @throw parse_error.108  if character '~' is not followed by '0' or '1'
+      */
+    static std::vector<std::string> split(const std::string& reference_string)
+    {
+        std::vector<std::string> result;
+
+        // special case: empty reference string -> no reference tokens
+        if (reference_string.empty())
+        {
+            return result;
+        }
+
+        // check if nonempty reference string begins with slash
+        if (reference_string[0] != '/')
+        {
+            JSON_THROW(detail::parse_error::create(
+                           107, 1,
+                           "JSON pointer must be empty or begin with '/' - was: '" +
+                           reference_string + "'"));
+        }
+
+        // extract the reference tokens:
+        // - slash: position of the last read slash (or end of string)
+        // - start: position after the previous slash
+        for (
+            // search for the first slash after the first character
+            size_t slash = reference_string.find_first_of('/', 1),
+            // set the beginning of the first reference token
+            start = 1;
+            // we can stop if start == string::npos+1 = 0
+            start != 0;
+            // set the beginning of the next reference token
+            // (will eventually be 0 if slash == std::string::npos)
+            start = slash + 1,
+            // find next slash
+            slash = reference_string.find_first_of('/', start))
+        {
+            // use the text between the beginning of the reference token
+            // (start) and the last slash (slash).
+            auto reference_token = reference_string.substr(start, slash - start);
+
+            // check reference tokens are properly escaped
+            for (size_t pos = reference_token.find_first_of('~');
+                    pos != std::string::npos;
+                    pos = reference_token.find_first_of('~', pos + 1))
+            {
+                assert(reference_token[pos] == '~');
+
+                // ~ must be followed by 0 or 1
+                if (pos == reference_token.size() - 1 or
+                        (reference_token[pos + 1] != '0' and
+                         reference_token[pos + 1] != '1'))
+                {
+                    JSON_THROW(detail::parse_error::create(
+                                   108, 0, "escape character '~' must be followed with '0' or '1'"));
+                }
+            }
+
+            // finally, store the reference token
+            unescape(reference_token);
+            result.push_back(reference_token);
+        }
+
+        return result;
+    }
+
+    /*!
+      @brief replace all occurrences of a substring by another string
+
+      @param[in,out] s  the string to manipulate; changed so that all
+      occurrences of @a f are replaced with @a t
+      @param[in]     f  the substring to replace with @a t
+      @param[in]     t  the string to replace @a f
+
+      @pre The search string @a f must not be empty. **This precondition is
+      enforced with an assertion.**
+
+      @since version 2.0.0
+      */
+    static void replace_substring(std::string& s, const std::string& f,
+                                  const std::string& t)
+    {
+        assert(not f.empty());
+
+        for (size_t pos = s.find(f);         // find first occurrence of f
+                pos != std::string::npos;       // make sure f was found
+                s.replace(pos, f.size(), t),    // replace with t
+                pos = s.find(f, pos + t.size()) // find next occurrence of f
+            )
+            ;
+    }
+
+    /// escape tilde and slash
+    static std::string escape(std::string s)
+    {
+        // escape "~"" to "~0" and "/" to "~1"
+        replace_substring(s, "~", "~0");
+        replace_substring(s, "/", "~1");
+        return s;
+    }
+
+    /// unescape tilde and slash
+    static void unescape(std::string& s)
+    {
+        // first transform any occurrence of the sequence '~1' to '/'
+        replace_substring(s, "~1", "/");
+        // then transform any occurrence of the sequence '~0' to '~'
+        replace_substring(s, "~0", "~");
+    }
+
+    /*!
+      @param[in] reference_string  the reference string to the current value
+      @param[in] value             the value to consider
+      @param[in,out] result        the result object to insert values to
+
+      @note Empty objects or arrays are flattened to `null`.
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    static void flatten(const std::string& reference_string,
+                        const NLOHMANN_BASIC_JSON_TPL& value,
+                        NLOHMANN_BASIC_JSON_TPL& result);
+
+    /*!
+      @param[in] value  flattened JSON
+
+      @return unflattened JSON
+
+      @throw parse_error.109 if array index is not a number
+      @throw type_error.314  if value is not an object
+      @throw type_error.315  if object values are not primitive
+      @throw type_error.313  if value cannot be unflattened
+      */
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    static NLOHMANN_BASIC_JSON_TPL
+    unflatten(const NLOHMANN_BASIC_JSON_TPL& value);
+
+    friend bool operator==(json_pointer const& lhs,
+                           json_pointer const& rhs) noexcept;
+
+    friend bool operator!=(json_pointer const& lhs,
+                           json_pointer const& rhs) noexcept;
+
+    /// the reference tokens
+    std::vector<std::string> reference_tokens;
+};
 
 /*!
 @brief a class to store JSON values
@@ -1409,13 +1737,14 @@ class basic_json
     template<detail::value_t> friend struct detail::external_constructor;
     /// workaround type for MSVC
     using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
+    friend ::nlohmann::json_pointer;
 
   public:
     using value_t = detail::value_t;
     // forward declarations
     template<typename U> class iter_impl;
     template<typename Base> class json_reverse_iterator;
-    class json_pointer;
+    using json_pointer = ::nlohmann::json_pointer;
     template<typename T, typename SFINAE>
     using json_serializer = JSONSerializer<T, SFINAE>;
 
@@ -13031,685 +13360,6 @@ scan_number_done:
     };
 
   public:
-    /*!
-    @brief JSON Pointer
-
-    A JSON pointer defines a string syntax for identifying a specific value
-    within a JSON document. It can be used with functions `at` and
-    `operator[]`. Furthermore, JSON pointers are the base for JSON patches.
-
-    @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
-
-    @since version 2.0.0
-    */
-    class json_pointer
-    {
-        /// allow basic_json to access private members
-        friend class basic_json;
-
-      public:
-        /*!
-        @brief create JSON pointer
-
-        Create a JSON pointer according to the syntax described in
-        [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
-
-        @param[in] s  string representing the JSON pointer; if omitted, the
-                      empty string is assumed which references the whole JSON
-                      value
-
-        @throw parse_error.107 if the given JSON pointer @a s is nonempty and
-        does not begin with a slash (`/`); see example below
-
-        @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s
-        is not followed by `0` (representing `~`) or `1` (representing `/`);
-        see example below
-
-        @liveexample{The example shows the construction several valid JSON
-        pointers as well as the exceptional behavior.,json_pointer}
-
-        @since version 2.0.0
-        */
-        explicit json_pointer(const std::string& s = "")
-            : reference_tokens(split(s))
-        {}
-
-        /*!
-        @brief return a string representation of the JSON pointer
-
-        @invariant For each JSON pointer `ptr`, it holds:
-        @code {.cpp}
-        ptr == json_pointer(ptr.to_string());
-        @endcode
-
-        @return a string representation of the JSON pointer
-
-        @liveexample{The example shows the result of `to_string`.,
-        json_pointer__to_string}
-
-        @since version 2.0.0
-        */
-        std::string to_string() const noexcept
-        {
-            return std::accumulate(reference_tokens.begin(),
-                                   reference_tokens.end(), std::string{},
-                                   [](const std::string & a, const std::string & b)
-            {
-                return a + "/" + escape(b);
-            });
-        }
-
-        /// @copydoc to_string()
-        operator std::string() const
-        {
-            return to_string();
-        }
-
-      private:
-        /*!
-        @brief remove and return last reference pointer
-        @throw out_of_range.405 if JSON pointer has no parent
-        */
-        std::string pop_back()
-        {
-            if (is_root())
-            {
-                JSON_THROW(out_of_range::create(405, "JSON pointer has no parent"));
-            }
-
-            auto last = reference_tokens.back();
-            reference_tokens.pop_back();
-            return last;
-        }
-
-        /// return whether pointer points to the root document
-        bool is_root() const
-        {
-            return reference_tokens.empty();
-        }
-
-        json_pointer top() const
-        {
-            if (is_root())
-            {
-                JSON_THROW(out_of_range::create(405, "JSON pointer has no parent"));
-            }
-
-            json_pointer result = *this;
-            result.reference_tokens = {reference_tokens[0]};
-            return result;
-        }
-
-        /*!
-        @brief create and return a reference to the pointed to value
-
-        @complexity Linear in the number of reference tokens.
-
-        @throw parse_error.109 if array index is not a number
-        @throw type_error.313 if value cannot be unflattened
-        */
-        reference get_and_create(reference j) const
-        {
-            pointer result = &j;
-
-            // in case no reference tokens exist, return a reference to the
-            // JSON value j which will be overwritten by a primitive value
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (result->m_type)
-                {
-                    case value_t::null:
-                    {
-                        if (reference_token == "0")
-                        {
-                            // start a new array if reference token is 0
-                            result = &result->operator[](0);
-                        }
-                        else
-                        {
-                            // start a new object otherwise
-                            result = &result->operator[](reference_token);
-                        }
-                        break;
-                    }
-
-                    case value_t::object:
-                    {
-                        // create an entry in the object
-                        result = &result->operator[](reference_token);
-                        break;
-                    }
-
-                    case value_t::array:
-                    {
-                        // create an entry in the array
-                        JSON_TRY
-                        {
-                            result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
-                        }
-                        JSON_CATCH (std::invalid_argument&)
-                        {
-                            JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
-                        }
-                        break;
-                    }
-
-                    /*
-                    The following code is only reached if there exists a
-                    reference token _and_ the current value is primitive. In
-                    this case, we have an error situation, because primitive
-                    values may only occur as single value; that is, with an
-                    empty list of reference tokens.
-                    */
-                    default:
-                    {
-                        JSON_THROW(type_error::create(313, "invalid value to unflatten"));
-                    }
-                }
-            }
-
-            return *result;
-        }
-
-        /*!
-        @brief return a reference to the pointed to value
-
-        @note This version does not throw if a value is not present, but tries
-        to create nested values instead. For instance, calling this function
-        with pointer `"/this/that"` on a null value is equivalent to calling
-        `operator[]("this").operator[]("that")` on that value, effectively
-        changing the null value to an object.
-
-        @param[in] ptr  a JSON value
-
-        @return reference to the JSON value pointed to by the JSON pointer
-
-        @complexity Linear in the length of the JSON pointer.
-
-        @throw parse_error.106   if an array index begins with '0'
-        @throw parse_error.109   if an array index was not a number
-        @throw out_of_range.404  if the JSON pointer can not be resolved
-        */
-        reference get_unchecked(pointer ptr) const
-        {
-            for (const auto& reference_token : reference_tokens)
-            {
-                // convert null values to arrays or objects before continuing
-                if (ptr->m_type == value_t::null)
-                {
-                    // check if reference token is a number
-                    const bool nums = std::all_of(reference_token.begin(),
-                                                  reference_token.end(),
-                                                  [](const char x)
-                    {
-                        return (x >= '0' and x <= '9');
-                    });
-
-                    // change value to array for numbers or "-" or to object
-                    // otherwise
-                    if (nums or reference_token == "-")
-                    {
-                        *ptr = value_t::array;
-                    }
-                    else
-                    {
-                        *ptr = value_t::object;
-                    }
-                }
-
-                switch (ptr->m_type)
-                {
-                    case value_t::object:
-                    {
-                        // use unchecked object access
-                        ptr = &ptr->operator[](reference_token);
-                        break;
-                    }
-
-                    case value_t::array:
-                    {
-                        // error condition (cf. RFC 6901, Sect. 4)
-                        if (reference_token.size() > 1 and reference_token[0] == '0')
-                        {
-                            JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
-                        }
-
-                        if (reference_token == "-")
-                        {
-                            // explicitly treat "-" as index beyond the end
-                            ptr = &ptr->operator[](ptr->m_value.array->size());
-                        }
-                        else
-                        {
-                            // convert array index to number; unchecked access
-                            JSON_TRY
-                            {
-                                ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
-                            }
-                            JSON_CATCH (std::invalid_argument&)
-                            {
-                                JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
-                            }
-                        }
-                        break;
-                    }
-
-                    default:
-                    {
-                        JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
-                    }
-                }
-            }
-
-            return *ptr;
-        }
-
-        /*!
-        @throw parse_error.106   if an array index begins with '0'
-        @throw parse_error.109   if an array index was not a number
-        @throw out_of_range.402  if the array index '-' is used
-        @throw out_of_range.404  if the JSON pointer can not be resolved
-        */
-        reference get_checked(pointer ptr) const
-        {
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (ptr->m_type)
-                {
-                    case value_t::object:
-                    {
-                        // note: at performs range check
-                        ptr = &ptr->at(reference_token);
-                        break;
-                    }
-
-                    case value_t::array:
-                    {
-                        if (reference_token == "-")
-                        {
-                            // "-" always fails the range check
-                            JSON_THROW(out_of_range::create(402, "array index '-' (" +
-                                                            std::to_string(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
-                        }
-
-                        // error condition (cf. RFC 6901, Sect. 4)
-                        if (reference_token.size() > 1 and reference_token[0] == '0')
-                        {
-                            JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
-                        }
-
-                        // note: at performs range check
-                        JSON_TRY
-                        {
-                            ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
-                        }
-                        JSON_CATCH (std::invalid_argument&)
-                        {
-                            JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
-                        }
-                        break;
-                    }
-
-                    default:
-                    {
-                        JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
-                    }
-                }
-            }
-
-            return *ptr;
-        }
-
-        /*!
-        @brief return a const reference to the pointed to value
-
-        @param[in] ptr  a JSON value
-
-        @return const reference to the JSON value pointed to by the JSON
-                pointer
-
-        @throw parse_error.106   if an array index begins with '0'
-        @throw parse_error.109   if an array index was not a number
-        @throw out_of_range.402  if the array index '-' is used
-        @throw out_of_range.404  if the JSON pointer can not be resolved
-        */
-        const_reference get_unchecked(const_pointer ptr) const
-        {
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (ptr->m_type)
-                {
-                    case value_t::object:
-                    {
-                        // use unchecked object access
-                        ptr = &ptr->operator[](reference_token);
-                        break;
-                    }
-
-                    case value_t::array:
-                    {
-                        if (reference_token == "-")
-                        {
-                            // "-" cannot be used for const access
-                            JSON_THROW(out_of_range::create(402, "array index '-' (" +
-                                                            std::to_string(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
-                        }
-
-                        // error condition (cf. RFC 6901, Sect. 4)
-                        if (reference_token.size() > 1 and reference_token[0] == '0')
-                        {
-                            JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
-                        }
-
-                        // use unchecked array access
-                        JSON_TRY
-                        {
-                            ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
-                        }
-                        JSON_CATCH (std::invalid_argument&)
-                        {
-                            JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
-                        }
-                        break;
-                    }
-
-                    default:
-                    {
-                        JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
-                    }
-                }
-            }
-
-            return *ptr;
-        }
-
-        /*!
-        @throw parse_error.106   if an array index begins with '0'
-        @throw parse_error.109   if an array index was not a number
-        @throw out_of_range.402  if the array index '-' is used
-        @throw out_of_range.404  if the JSON pointer can not be resolved
-        */
-        const_reference get_checked(const_pointer ptr) const
-        {
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (ptr->m_type)
-                {
-                    case value_t::object:
-                    {
-                        // note: at performs range check
-                        ptr = &ptr->at(reference_token);
-                        break;
-                    }
-
-                    case value_t::array:
-                    {
-                        if (reference_token == "-")
-                        {
-                            // "-" always fails the range check
-                            JSON_THROW(out_of_range::create(402, "array index '-' (" +
-                                                            std::to_string(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
-                        }
-
-                        // error condition (cf. RFC 6901, Sect. 4)
-                        if (reference_token.size() > 1 and reference_token[0] == '0')
-                        {
-                            JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
-                        }
-
-                        // note: at performs range check
-                        JSON_TRY
-                        {
-                            ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
-                        }
-                        JSON_CATCH (std::invalid_argument&)
-                        {
-                            JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
-                        }
-                        break;
-                    }
-
-                    default:
-                    {
-                        JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
-                    }
-                }
-            }
-
-            return *ptr;
-        }
-
-        /*!
-        @brief split the string input to reference tokens
-
-        @note This function is only called by the json_pointer constructor.
-              All exceptions below are documented there.
-
-        @throw parse_error.107  if the pointer is not empty or begins with '/'
-        @throw parse_error.108  if character '~' is not followed by '0' or '1'
-        */
-        static std::vector<std::string> split(const std::string& reference_string)
-        {
-            std::vector<std::string> result;
-
-            // special case: empty reference string -> no reference tokens
-            if (reference_string.empty())
-            {
-                return result;
-            }
-
-            // check if nonempty reference string begins with slash
-            if (reference_string[0] != '/')
-            {
-                JSON_THROW(parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'"));
-            }
-
-            // extract the reference tokens:
-            // - slash: position of the last read slash (or end of string)
-            // - start: position after the previous slash
-            for (
-                // search for the first slash after the first character
-                size_t slash = reference_string.find_first_of('/', 1),
-                // set the beginning of the first reference token
-                start = 1;
-                // we can stop if start == string::npos+1 = 0
-                start != 0;
-                // set the beginning of the next reference token
-                // (will eventually be 0 if slash == std::string::npos)
-                start = slash + 1,
-                // find next slash
-                slash = reference_string.find_first_of('/', start))
-            {
-                // use the text between the beginning of the reference token
-                // (start) and the last slash (slash).
-                auto reference_token = reference_string.substr(start, slash - start);
-
-                // check reference tokens are properly escaped
-                for (size_t pos = reference_token.find_first_of('~');
-                        pos != std::string::npos;
-                        pos = reference_token.find_first_of('~', pos + 1))
-                {
-                    assert(reference_token[pos] == '~');
-
-                    // ~ must be followed by 0 or 1
-                    if (pos == reference_token.size() - 1 or
-                            (reference_token[pos + 1] != '0' and
-                             reference_token[pos + 1] != '1'))
-                    {
-                        JSON_THROW(parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
-                    }
-                }
-
-                // finally, store the reference token
-                unescape(reference_token);
-                result.push_back(reference_token);
-            }
-
-            return result;
-        }
-
-        /*!
-        @brief replace all occurrences of a substring by another string
-
-        @param[in,out] s  the string to manipulate; changed so that all
-                          occurrences of @a f are replaced with @a t
-        @param[in]     f  the substring to replace with @a t
-        @param[in]     t  the string to replace @a f
-
-        @pre The search string @a f must not be empty. **This precondition is
-             enforced with an assertion.**
-
-        @since version 2.0.0
-        */
-        static void replace_substring(std::string& s,
-                                      const std::string& f,
-                                      const std::string& t)
-        {
-            assert(not f.empty());
-
-            for (
-                size_t pos = s.find(f);         // find first occurrence of f
-                pos != std::string::npos;       // make sure f was found
-                s.replace(pos, f.size(), t),    // replace with t
-                pos = s.find(f, pos + t.size()) // find next occurrence of f
-            );
-        }
-
-        /// escape tilde and slash
-        static std::string escape(std::string s)
-        {
-            // escape "~"" to "~0" and "/" to "~1"
-            replace_substring(s, "~", "~0");
-            replace_substring(s, "/", "~1");
-            return s;
-        }
-
-        /// unescape tilde and slash
-        static void unescape(std::string& s)
-        {
-            // first transform any occurrence of the sequence '~1' to '/'
-            replace_substring(s, "~1", "/");
-            // then transform any occurrence of the sequence '~0' to '~'
-            replace_substring(s, "~0", "~");
-        }
-
-        /*!
-        @param[in] reference_string  the reference string to the current value
-        @param[in] value             the value to consider
-        @param[in,out] result        the result object to insert values to
-
-        @note Empty objects or arrays are flattened to `null`.
-        */
-        static void flatten(const std::string& reference_string,
-                            const basic_json& value,
-                            basic_json& result)
-        {
-            switch (value.m_type)
-            {
-                case value_t::array:
-                {
-                    if (value.m_value.array->empty())
-                    {
-                        // flatten empty array as null
-                        result[reference_string] = nullptr;
-                    }
-                    else
-                    {
-                        // iterate array and use index as reference string
-                        for (size_t i = 0; i < value.m_value.array->size(); ++i)
-                        {
-                            flatten(reference_string + "/" + std::to_string(i),
-                                    value.m_value.array->operator[](i), result);
-                        }
-                    }
-                    break;
-                }
-
-                case value_t::object:
-                {
-                    if (value.m_value.object->empty())
-                    {
-                        // flatten empty object as null
-                        result[reference_string] = nullptr;
-                    }
-                    else
-                    {
-                        // iterate object and use keys as reference string
-                        for (const auto& element : *value.m_value.object)
-                        {
-                            flatten(reference_string + "/" + escape(element.first),
-                                    element.second, result);
-                        }
-                    }
-                    break;
-                }
-
-                default:
-                {
-                    // add primitive value with its reference string
-                    result[reference_string] = value;
-                    break;
-                }
-            }
-        }
-
-        /*!
-        @param[in] value  flattened JSON
-
-        @return unflattened JSON
-
-        @throw parse_error.109 if array index is not a number
-        @throw type_error.314  if value is not an object
-        @throw type_error.315  if object values are not primitive
-        @throw type_error.313  if value cannot be unflattened
-        */
-        static basic_json unflatten(const basic_json& value)
-        {
-            if (not value.is_object())
-            {
-                JSON_THROW(type_error::create(314, "only objects can be unflattened"));
-            }
-
-            basic_json result;
-
-            // iterate the JSON object values
-            for (const auto& element : *value.m_value.object)
-            {
-                if (not element.second.is_primitive())
-                {
-                    JSON_THROW(type_error::create(315, "values in object must be primitive"));
-                }
-
-                // assign value to reference pointed to by JSON pointer; Note
-                // that if the JSON pointer is "" (i.e., points to the whole
-                // value), function get_and_create returns a reference to
-                // result itself. An assignment will then create a primitive
-                // value.
-                json_pointer(element.first).get_and_create(result) = element.second;
-            }
-
-            return result;
-        }
-
-        friend bool operator==(json_pointer const& lhs,
-                               json_pointer const& rhs) noexcept
-        {
-            return lhs.reference_tokens == rhs.reference_tokens;
-        }
-
-        friend bool operator!=(json_pointer const& lhs,
-                               json_pointer const& rhs) noexcept
-        {
-            return !(lhs == rhs);
-        }
-
-        /// the reference tokens
-        std::vector<std::string> reference_tokens {};
-    };
-
     //////////////////////////
     // JSON Pointer support //
     //////////////////////////
@@ -14423,6 +14073,432 @@ uses the standard template types.
 @since version 1.0.0
 */
 using json = basic_json<>;
+
+//////////////////
+// json_pointer //
+//////////////////
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+NLOHMANN_BASIC_JSON_TPL&
+json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const
+{
+    using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
+    auto result = &j;
+
+    // in case no reference tokens exist, return a reference to the
+    // JSON value j which will be overwritten by a primitive value
+    for (const auto& reference_token : reference_tokens)
+    {
+        switch (result->m_type)
+        {
+            case detail::value_t::null:
+            {
+                if (reference_token == "0")
+                {
+                    // start a new array if reference token is 0
+                    result = &result->operator[](0);
+                }
+                else
+                {
+                    // start a new object otherwise
+                    result = &result->operator[](reference_token);
+                }
+                break;
+            }
+
+            case detail::value_t::object:
+            {
+                // create an entry in the object
+                result = &result->operator[](reference_token);
+                break;
+            }
+
+            case detail::value_t::array:
+            {
+                // create an entry in the array
+                JSON_TRY
+                {
+                    result = &result->operator[](
+                        static_cast<size_type>(std::stoi(reference_token)));
+                }
+                JSON_CATCH(std::invalid_argument&)
+                {
+                    JSON_THROW(detail::parse_error::create(
+                                   109, 0, "array index '" + reference_token + "' is not a number"));
+                }
+                break;
+            }
+
+            /*
+            The following code is only reached if there exists a
+            reference token _and_ the current value is primitive. In
+            this case, we have an error situation, because primitive
+            values may only occur as single value; that is, with an
+            empty list of reference tokens.
+            */
+            default:
+            {
+                JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
+            }
+        }
+    }
+
+    return *result;
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+NLOHMANN_BASIC_JSON_TPL&
+json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const
+{
+    using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
+    for (const auto& reference_token : reference_tokens)
+    {
+        // convert null values to arrays or objects before continuing
+        if (ptr->m_type == detail::value_t::null)
+        {
+            // check if reference token is a number
+            const bool nums =
+                std::all_of(reference_token.begin(), reference_token.end(),
+                            [](const char x)
+            {
+                return (x >= '0' and x <= '9');
+            });
+
+            // change value to array for numbers or "-" or to object
+            // otherwise
+            if (nums or reference_token == "-")
+            {
+                *ptr = detail::value_t::array;
+            }
+            else
+            {
+                *ptr = detail::value_t::object;
+            }
+        }
+
+        switch (ptr->m_type)
+        {
+            case detail::value_t::object:
+            {
+                // use unchecked object access
+                ptr = &ptr->operator[](reference_token);
+                break;
+            }
+
+            case detail::value_t::array:
+            {
+                // error condition (cf. RFC 6901, Sect. 4)
+                if (reference_token.size() > 1 and reference_token[0] == '0')
+                {
+                    JSON_THROW(detail::parse_error::create(106, 0,
+                                                           "array index '" + reference_token +
+                                                           "' must not begin with '0'"));
+                }
+
+                if (reference_token == "-")
+                {
+                    // explicitly treat "-" as index beyond the end
+                    ptr = &ptr->operator[](ptr->m_value.array->size());
+                }
+                else
+                {
+                    // convert array index to number; unchecked access
+                    JSON_TRY
+                    {
+                        ptr = &ptr->operator[](
+                            static_cast<size_type>(std::stoi(reference_token)));
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(
+                                       109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                }
+                break;
+            }
+
+            default:
+            {
+                JSON_THROW(detail::out_of_range::create(
+                               404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+    }
+
+    return *ptr;
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+NLOHMANN_BASIC_JSON_TPL&
+json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const
+{
+    using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
+    for (const auto& reference_token : reference_tokens)
+    {
+        switch (ptr->m_type)
+        {
+            case detail::value_t::object:
+            {
+                // note: at performs range check
+                ptr = &ptr->at(reference_token);
+                break;
+            }
+
+            case detail::value_t::array:
+            {
+                if (reference_token == "-")
+                {
+                    // "-" always fails the range check
+                    JSON_THROW(detail::out_of_range::create(
+                                   402,
+                                   "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                   ") is out of range"));
+                }
+
+                // error condition (cf. RFC 6901, Sect. 4)
+                if (reference_token.size() > 1 and reference_token[0] == '0')
+                {
+                    JSON_THROW(detail::parse_error::create(106, 0,
+                                                           "array index '" + reference_token +
+                                                           "' must not begin with '0'"));
+                }
+
+                // note: at performs range check
+                JSON_TRY
+                {
+                    ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
+                }
+                JSON_CATCH(std::invalid_argument&)
+                {
+                    JSON_THROW(detail::parse_error::create(
+                                   109, 0, "array index '" + reference_token + "' is not a number"));
+                }
+                break;
+            }
+
+            default:
+            {
+                JSON_THROW(detail::out_of_range::create(
+                               404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+    }
+
+    return *ptr;
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+const NLOHMANN_BASIC_JSON_TPL&
+json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const
+{
+    using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
+    for (const auto& reference_token : reference_tokens)
+    {
+        switch (ptr->m_type)
+        {
+            case detail::value_t::object:
+            {
+                // use unchecked object access
+                ptr = &ptr->operator[](reference_token);
+                break;
+            }
+
+            case detail::value_t::array:
+            {
+                if (reference_token == "-")
+                {
+                    // "-" cannot be used for const access
+                    JSON_THROW(detail::out_of_range::create(
+                                   402,
+                                   "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                   ") is out of range"));
+                }
+
+                // error condition (cf. RFC 6901, Sect. 4)
+                if (reference_token.size() > 1 and reference_token[0] == '0')
+                {
+                    JSON_THROW(detail::parse_error::create(106, 0,
+                                                           "array index '" + reference_token +
+                                                           "' must not begin with '0'"));
+                }
+
+                // use unchecked array access
+                JSON_TRY
+                {
+                    ptr = &ptr->operator[](
+                        static_cast<size_type>(std::stoi(reference_token)));
+                }
+                JSON_CATCH(std::invalid_argument&)
+                {
+                    JSON_THROW(detail::parse_error::create(
+                                   109, 0, "array index '" + reference_token + "' is not a number"));
+                }
+                break;
+            }
+
+            default:
+            {
+                JSON_THROW(detail::out_of_range::create(
+                               404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+    }
+
+    return *ptr;
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+const NLOHMANN_BASIC_JSON_TPL&
+json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const
+{
+    using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
+    for (const auto& reference_token : reference_tokens)
+    {
+        switch (ptr->m_type)
+        {
+            case detail::value_t::object:
+            {
+                // note: at performs range check
+                ptr = &ptr->at(reference_token);
+                break;
+            }
+
+            case detail::value_t::array:
+            {
+                if (reference_token == "-")
+                {
+                    // "-" always fails the range check
+                    JSON_THROW(detail::out_of_range::create(
+                                   402,
+                                   "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                   ") is out of range"));
+                }
+
+                // error condition (cf. RFC 6901, Sect. 4)
+                if (reference_token.size() > 1 and reference_token[0] == '0')
+                {
+                    JSON_THROW(detail::parse_error::create(106, 0,
+                                                           "array index '" + reference_token +
+                                                           "' must not begin with '0'"));
+                }
+
+                // note: at performs range check
+                JSON_TRY
+                {
+                    ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
+                }
+                JSON_CATCH(std::invalid_argument&)
+                {
+                    JSON_THROW(detail::parse_error::create(
+                                   109, 0, "array index '" + reference_token + "' is not a number"));
+                }
+                break;
+            }
+
+            default:
+            {
+                JSON_THROW(detail::out_of_range::create(
+                               404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+    }
+
+    return *ptr;
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+void json_pointer::flatten(const std::string& reference_string,
+                           const NLOHMANN_BASIC_JSON_TPL& value,
+                           NLOHMANN_BASIC_JSON_TPL& result)
+{
+    switch (value.m_type)
+    {
+        case detail::value_t::array:
+        {
+            if (value.m_value.array->empty())
+            {
+                // flatten empty array as null
+                result[reference_string] = nullptr;
+            }
+            else
+            {
+                // iterate array and use index as reference string
+                for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                {
+                    flatten(reference_string + "/" + std::to_string(i),
+                            value.m_value.array->operator[](i), result);
+                }
+            }
+            break;
+        }
+
+        case detail::value_t::object:
+        {
+            if (value.m_value.object->empty())
+            {
+                // flatten empty object as null
+                result[reference_string] = nullptr;
+            }
+            else
+            {
+                // iterate object and use keys as reference string
+                for (const auto& element : *value.m_value.object)
+                {
+                    flatten(reference_string + "/" + escape(element.first), element.second,
+                            result);
+                }
+            }
+            break;
+        }
+
+        default:
+        {
+            // add primitive value with its reference string
+            result[reference_string] = value;
+            break;
+        }
+    }
+}
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+NLOHMANN_BASIC_JSON_TPL
+json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value)
+{
+    if (not value.is_object())
+    {
+        JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
+    }
+
+    NLOHMANN_BASIC_JSON_TPL result;
+
+    // iterate the JSON object values
+    for (const auto& element : *value.m_value.object)
+    {
+        if (not element.second.is_primitive())
+        {
+            JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
+        }
+
+        // assign value to reference pointed to by JSON pointer; Note
+        // that if the JSON pointer is "" (i.e., points to the whole
+        // value), function get_and_create returns a reference to
+        // result itself. An assignment will then create a primitive
+        // value.
+        json_pointer(element.first).get_and_create(result) = element.second;
+    }
+
+    return result;
+}
+
+inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept
+{
+    return lhs.reference_tokens == rhs.reference_tokens;
+}
+
+inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept
+{
+    return !(lhs == rhs);
+}
 } // namespace nlohmann