From 726051e9b75009e68bb216171ac8e1ad56fdb99a Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Mon, 11 Apr 2016 23:17:03 +0200
Subject: [PATCH 01/13] very first draft of a JSON pointer API

---
 src/json.hpp      | 73 +++++++++++++++++++++++++++++++++++++++++++++++
 src/json.hpp.re2c | 73 +++++++++++++++++++++++++++++++++++++++++++++++
 test/unit.cpp     | 29 +++++++++++++++++++
 3 files changed, 175 insertions(+)

diff --git a/src/json.hpp b/src/json.hpp
index 8f671fb0..c8b81504 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8844,6 +8844,79 @@ basic_json_parser_64:
         /// the lexer
         lexer m_lexer;
     };
+
+  public:
+    class json_pointer
+    {
+      public:
+        /// empty reference token
+        json_pointer() = default;
+
+        /// nonempty reference token
+        json_pointer(const std::string& s)
+        {
+            split(s);
+        }
+
+        /// return referenced value
+        reference get(reference j)
+        {
+            reference result = j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result.m_type)
+                {
+                    case value_t::object:
+                        result = result[reference_token];
+                        continue;
+
+                    case value_t::array:
+                        result = result[std::stoi(reference_token)];
+                        continue;
+
+                    default:
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                }
+            }
+
+            return result;
+        }
+
+      private:
+        /// the reference tokens
+        std::vector<std::string> reference_tokens {};
+
+        /// split the string input to reference tokens
+        void split(std::string reference_string)
+        {
+            // special case: empty reference string -> no reference tokens
+            if (reference_string.empty())
+            {
+                return;
+            }
+
+            // check if nonempty reference string begins with slash
+            if (reference_string[0] != '/')
+            {
+                throw std::domain_error("JSON pointer must be empty or begin with '/'");
+            }
+
+            // tokenize reference string
+            auto ptr = std::strtok(&reference_string[0], "/");
+            while (ptr != nullptr)
+            {
+                reference_tokens.push_back(ptr);
+                ptr = std::strtok(NULL, "/");
+            }
+
+            // special case: reference string was just "/"
+            if (reference_tokens.empty())
+            {
+                reference_tokens.push_back("");
+            }
+        }
+    };
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index ebf83d83..164f4962 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8123,6 +8123,79 @@ class basic_json
         /// the lexer
         lexer m_lexer;
     };
+
+  public:
+    class json_pointer
+    {
+      public:
+        /// empty reference token
+        json_pointer() = default;
+
+        /// nonempty reference token
+        json_pointer(const std::string& s)
+        {
+            split(s);
+        }
+
+        /// return referenced value
+        reference get(reference j)
+        {
+            reference result = j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result.m_type)
+                {
+                    case value_t::object:
+                        result = result[reference_token];
+                        continue;
+
+                    case value_t::array:
+                        result = result[std::stoi(reference_token)];
+                        continue;
+
+                    default:
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                }
+            }
+
+            return result;
+        }
+
+      private:
+        /// the reference tokens
+        std::vector<std::string> reference_tokens {};
+
+        /// split the string input to reference tokens
+        void split(std::string reference_string)
+        {
+            // special case: empty reference string -> no reference tokens
+            if (reference_string.empty())
+            {
+                return;
+            }
+
+            // check if nonempty reference string begins with slash
+            if (reference_string[0] != '/')
+            {
+                throw std::domain_error("JSON pointer must be empty or begin with '/'");
+            }
+
+            // tokenize reference string
+            auto ptr = std::strtok(&reference_string[0], "/");
+            while (ptr != nullptr)
+            {
+                reference_tokens.push_back(ptr);
+                ptr = std::strtok(NULL, "/");
+            }
+
+            // special case: reference string was just "/"
+            if (reference_tokens.empty())
+            {
+                reference_tokens.push_back("");
+            }
+        }
+    };
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index ab96364c..6051ee3e 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12052,6 +12052,35 @@ TEST_CASE("Unicode", "[hide]")
     }
 }
 
+TEST_CASE("JSON pointers")
+{
+    SECTION("examples from RFC 6901")
+    {
+        json j = R"(
+        {
+            "foo": ["bar", "baz"],
+            "": 0,
+            "a/b": 1,
+            "c%d": 2,
+            "e^f": 3,
+            "g|h": 4,
+            "i\\j": 5,
+            "k\"l": 6,
+            " ": 7,
+            "m~n": 8
+        }
+        )"_json;
+
+        json::json_pointer jp0("");
+        json::json_pointer jp1("/foo");
+        //json::json_pointer jp2("/foo/0");
+
+        auto jp0_ = jp0.get(j);
+        auto jp1_ = jp1.get(j);
+        //auto jp2_ = jp2.get(j);
+    }
+}
+
 TEST_CASE("regression tests")
 {
     SECTION("issue #60 - Double quotation mark is not parsed correctly")

From 2cb925c186e623f19b61fe0ce41bb772406df462 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Wed, 13 Apr 2016 17:41:19 +0200
Subject: [PATCH 02/13] adding support for escaped reference tokens

---
 src/json.hpp      | 57 ++++++++++++++++++++++++++++++++++++++++++-----
 src/json.hpp.re2c | 57 ++++++++++++++++++++++++++++++++++++++++++-----
 test/unit.cpp     | 53 +++++++++++++++++++++++++++++++++++++------
 3 files changed, 150 insertions(+), 17 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index c8b81504..f2c3813a 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8861,18 +8861,18 @@ basic_json_parser_64:
         /// return referenced value
         reference get(reference j)
         {
-            reference result = j;
+            pointer result = &j;
 
             for (const auto& reference_token : reference_tokens)
             {
-                switch (result.m_type)
+                switch (result->m_type)
                 {
                     case value_t::object:
-                        result = result[reference_token];
+                        result = &result->at(reference_token);
                         continue;
 
                     case value_t::array:
-                        result = result[std::stoi(reference_token)];
+                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
                         continue;
 
                     default:
@@ -8880,13 +8880,52 @@ basic_json_parser_64:
                 }
             }
 
-            return result;
+            return *result;
+        }
+
+        const_reference get(const_reference j) const
+        {
+            const_pointer result = &j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result->m_type)
+                {
+                    case value_t::object:
+                        result = &result->at(reference_token);
+                        continue;
+
+                    case value_t::array:
+                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+
+                    default:
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                }
+            }
+
+            return *result;
         }
 
       private:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
+        /// replace all occurrences of a substring by another string
+        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
+            );
+        }
+
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8915,6 +8954,14 @@ basic_json_parser_64:
             {
                 reference_tokens.push_back("");
             }
+
+            for (auto& reference_token : reference_tokens)
+            {
+                // first transform any occurrence of the sequence '~1' to '/'
+                replace_substring(reference_token, "~1", "/");
+                // then transform any occurrence of the sequence '~0' to '~'
+                replace_substring(reference_token, "~0", "~");
+            }
         }
     };
 };
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 164f4962..ea62dca5 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8140,18 +8140,18 @@ class basic_json
         /// return referenced value
         reference get(reference j)
         {
-            reference result = j;
+            pointer result = &j;
 
             for (const auto& reference_token : reference_tokens)
             {
-                switch (result.m_type)
+                switch (result->m_type)
                 {
                     case value_t::object:
-                        result = result[reference_token];
+                        result = &result->at(reference_token);
                         continue;
 
                     case value_t::array:
-                        result = result[std::stoi(reference_token)];
+                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
                         continue;
 
                     default:
@@ -8159,13 +8159,52 @@ class basic_json
                 }
             }
 
-            return result;
+            return *result;
+        }
+
+        const_reference get(const_reference j) const
+        {
+            const_pointer result = &j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result->m_type)
+                {
+                    case value_t::object:
+                        result = &result->at(reference_token);
+                        continue;
+
+                    case value_t::array:
+                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+
+                    default:
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                }
+            }
+
+            return *result;
         }
 
       private:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
+        /// replace all occurrences of a substring by another string
+        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
+            );
+        }
+
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8194,6 +8233,14 @@ class basic_json
             {
                 reference_tokens.push_back("");
             }
+
+            for (auto& reference_token : reference_tokens)
+            {
+                // first transform any occurrence of the sequence '~1' to '/'
+                replace_substring(reference_token, "~1", "/");
+                // then transform any occurrence of the sequence '~0' to '~'
+                replace_substring(reference_token, "~0", "~");
+            }
         }
     };
 };
diff --git a/test/unit.cpp b/test/unit.cpp
index 6051ee3e..d6ec00a7 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12071,13 +12071,53 @@ TEST_CASE("JSON pointers")
         }
         )"_json;
 
-        json::json_pointer jp0("");
-        json::json_pointer jp1("/foo");
-        //json::json_pointer jp2("/foo/0");
+        const json j_const = j;
 
-        auto jp0_ = jp0.get(j);
-        auto jp1_ = jp1.get(j);
-        //auto jp2_ = jp2.get(j);
+        SECTION("nonconst access")
+        {
+            // the whole document
+            CHECK(json::json_pointer().get(j) == j);
+            CHECK(json::json_pointer("").get(j) == j);
+
+            // array access
+            CHECK(json::json_pointer("/foo").get(j) == j["foo"]);
+            CHECK(json::json_pointer("/foo/0").get(j) == j["foo"][0]);
+            CHECK(json::json_pointer("/foo/1").get(j) == j["foo"][1]);
+
+            // empty string access
+            CHECK(json::json_pointer("/").get(j) == j[""]);
+
+            // other cases
+            CHECK(json::json_pointer("/ ").get(j) == j[" "]);
+            CHECK(json::json_pointer("/c%d").get(j) == j["c%d"]);
+            CHECK(json::json_pointer("/e^f").get(j) == j["e^f"]);
+            CHECK(json::json_pointer("/g|h").get(j) == j["g|h"]);
+            CHECK(json::json_pointer("/i\\j").get(j) == j["i\\j"]);
+            CHECK(json::json_pointer("/k\"l").get(j) == j["k\"l"]);
+
+            // escaped access
+            CHECK(json::json_pointer("/a~1b").get(j) == j["a/b"]);
+            CHECK(json::json_pointer("/m~0n").get(j) == j["m~n"]);
+
+            // unescaped access
+            CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
+            CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
+            // "/a/b" works for JSON {"a": {"b": 42}}
+            CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
+        }
+
+        SECTION("const access")
+        {
+            CHECK(j_const == json::json_pointer().get(j_const));
+            CHECK(j_const == json::json_pointer("").get(j_const));
+
+            CHECK(j_const["foo"] == json::json_pointer("/foo").get(j_const));
+            CHECK(j_const["foo"][0] == json::json_pointer("/foo/0").get(j_const));
+            CHECK(j_const["foo"][1] == json::json_pointer("/foo/1").get(j_const));
+
+            CHECK(j_const[""] == json::json_pointer("/").get(j_const));
+            CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const));
+        }
     }
 }
 
@@ -12437,4 +12477,3 @@ TEST_CASE("regression tests")
         CHECK(j3c.dump() == "1e04");
     }
 }
-

From 94af8abdff77de3526aca2c2ccfd03517ee678e5 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Wed, 13 Apr 2016 23:23:54 +0200
Subject: [PATCH 03/13] overworked reference token parsing

---
 src/json.hpp      | 84 ++++++++++++++++++++++++++++++++++++-----------
 src/json.hpp.re2c | 84 ++++++++++++++++++++++++++++++++++++-----------
 test/unit.cpp     | 23 +++++++++++++
 3 files changed, 151 insertions(+), 40 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index f2c3813a..84e10064 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8911,10 +8911,21 @@ basic_json_parser_64:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /// replace all occurrences of a substring by another string
-        void replace_substring(std::string& s,
-                               const std::string& f,
-                               const std::string& t)
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @f must not be empty.
+        */
+        static void replace_substring(std::string& s,
+                                      const std::string& f,
+                                      const std::string& t)
         {
             assert(not f.empty());
 
@@ -8941,26 +8952,49 @@ basic_json_parser_64:
                 throw std::domain_error("JSON pointer must be empty or begin with '/'");
             }
 
-            // tokenize reference string
-            auto ptr = std::strtok(&reference_string[0], "/");
-            while (ptr != nullptr)
+            // 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
+                // (could be 0 if slash == std::string::npos)
+                start = slash + 1,
+                // find next slash
+                slash = reference_string.find_first_of("/", start))
             {
-                reference_tokens.push_back(ptr);
-                ptr = std::strtok(NULL, "/");
-            }
+                // 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);
 
-            // special case: reference string was just "/"
-            if (reference_tokens.empty())
-            {
-                reference_tokens.push_back("");
-            }
+                // 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'))
+                    {
+                        throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
+                    }
+                }
 
-            for (auto& reference_token : reference_tokens)
-            {
                 // first transform any occurrence of the sequence '~1' to '/'
                 replace_substring(reference_token, "~1", "/");
                 // then transform any occurrence of the sequence '~0' to '~'
                 replace_substring(reference_token, "~0", "~");
+
+                // store the reference token
+                reference_tokens.push_back(reference_token);
             }
         }
     };
@@ -9026,9 +9060,9 @@ struct hash<nlohmann::json>
 /*!
 @brief user-defined string literal for JSON values
 
-This operator implements a user-defined string literal for JSON objects. It can
-be used by adding \p "_json" to a string literal and returns a JSON object if
-no parse error occurred.
+This operator implements a user-defined string literal for JSON objects. It
+can be used by adding \p "_json" to a string literal and returns a JSON object
+if no parse error occurred.
 
 @param[in] s  a string representation of a JSON object
 @return a JSON object
@@ -9040,6 +9074,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
     return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
 }
 
+/*!
+@brief user-defined string literal for JSON pointer
+
+@since version 2.0.0
+*/
+inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
+{
+    return nlohmann::json::json_pointer(s);
+}
+
 // restore GCC/clang diagnostic settings
 #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
     #pragma GCC diagnostic pop
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index ea62dca5..5501b35c 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8190,10 +8190,21 @@ class basic_json
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /// replace all occurrences of a substring by another string
-        void replace_substring(std::string& s,
-                               const std::string& f,
-                               const std::string& t)
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @f must not be empty.
+        */
+        static void replace_substring(std::string& s,
+                                      const std::string& f,
+                                      const std::string& t)
         {
             assert(not f.empty());
 
@@ -8220,26 +8231,49 @@ class basic_json
                 throw std::domain_error("JSON pointer must be empty or begin with '/'");
             }
 
-            // tokenize reference string
-            auto ptr = std::strtok(&reference_string[0], "/");
-            while (ptr != nullptr)
+            // 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
+                // (could be 0 if slash == std::string::npos)
+                start = slash + 1,
+                // find next slash
+                slash = reference_string.find_first_of("/", start))
             {
-                reference_tokens.push_back(ptr);
-                ptr = std::strtok(NULL, "/");
-            }
+                // 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);
 
-            // special case: reference string was just "/"
-            if (reference_tokens.empty())
-            {
-                reference_tokens.push_back("");
-            }
+                // 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'))
+                    {
+                        throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
+                    }
+                }
 
-            for (auto& reference_token : reference_tokens)
-            {
                 // first transform any occurrence of the sequence '~1' to '/'
                 replace_substring(reference_token, "~1", "/");
                 // then transform any occurrence of the sequence '~0' to '~'
                 replace_substring(reference_token, "~0", "~");
+
+                // store the reference token
+                reference_tokens.push_back(reference_token);
             }
         }
     };
@@ -8305,9 +8339,9 @@ struct hash<nlohmann::json>
 /*!
 @brief user-defined string literal for JSON values
 
-This operator implements a user-defined string literal for JSON objects. It can
-be used by adding \p "_json" to a string literal and returns a JSON object if
-no parse error occurred.
+This operator implements a user-defined string literal for JSON objects. It
+can be used by adding \p "_json" to a string literal and returns a JSON object
+if no parse error occurred.
 
 @param[in] s  a string representation of a JSON object
 @return a JSON object
@@ -8319,6 +8353,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
     return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
 }
 
+/*!
+@brief user-defined string literal for JSON pointer
+
+@since version 2.0.0
+*/
+inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
+{
+    return nlohmann::json::json_pointer(s);
+}
+
 // restore GCC/clang diagnostic settings
 #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
     #pragma GCC diagnostic pop
diff --git a/test/unit.cpp b/test/unit.cpp
index d6ec00a7..7c8fd8b7 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12118,6 +12118,29 @@ TEST_CASE("JSON pointers")
             CHECK(j_const[""] == json::json_pointer("/").get(j_const));
             CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const));
         }
+
+        SECTION("user-defined string literal")
+        {
+            // the whole document
+            CHECK(""_json_pointer.get(j) == j);
+
+            // array access
+            CHECK("/foo"_json_pointer.get(j) == j["foo"]);
+            CHECK("/foo/0"_json_pointer.get(j) == j["foo"][0]);
+            CHECK("/foo/1"_json_pointer.get(j) == j["foo"][1]);
+        }
+
+        SECTION("errors")
+        {
+            CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
+            CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
+
+            CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
+            CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
+
+            CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
+            CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
+        }
     }
 }
 

From 3401954f5b3b163c7b152a314f8e78b534eb54e1 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 16 Apr 2016 14:02:14 +0200
Subject: [PATCH 04/13] cleaned up API

---
 src/json.hpp      | 45 ++++++++++++++++++++++++++++++++++++---------
 src/json.hpp.re2c | 45 ++++++++++++++++++++++++++++++++++++---------
 test/unit.cpp     | 40 +++++++++++++++++++++++++++++++++-------
 3 files changed, 105 insertions(+), 25 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 34750884..2d383136 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -198,6 +198,9 @@ class basic_json
           AllocatorType>;
 
   public:
+    // forward declarations
+    template<typename Base> class json_reverse_iterator;
+    class json_pointer;
 
     /////////////////////
     // container types //
@@ -227,9 +230,6 @@ class basic_json
     /// the type of an element const pointer
     using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
 
-    // forward declaration
-    template<typename Base> class json_reverse_iterator;
-
     /// an iterator for a basic_json container
     class iterator;
     /// a const iterator for a basic_json container
@@ -3595,6 +3595,28 @@ class basic_json
         }
     }
 
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a reference to the element at with specified JSON pointer @a ptr.
+
+    @param p  JSON pointer to the desired element
+
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer& ptr)
+    {
+        return ptr.get(*this);
+    }
+
+    /*!
+    @copydoc basic_json::operator[](const json_pointer&)
+    */
+    const_reference operator[](const json_pointer& ptr) const
+    {
+        return ptr.get(*this);
+    }
+
     /*!
     @brief access specified object element with default value
 
@@ -8815,6 +8837,11 @@ basic_json_parser_63:
     };
 
   public:
+    /*!
+    @brief JSON Pointer
+
+    @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+    */
     class json_pointer
     {
       public:
@@ -8822,13 +8849,14 @@ basic_json_parser_63:
         json_pointer() = default;
 
         /// nonempty reference token
-        json_pointer(const std::string& s)
+        explicit json_pointer(const std::string& s)
         {
             split(s);
         }
 
+      private:
         /// return referenced value
-        reference get(reference j)
+        reference get(reference j) const
         {
             pointer result = &j;
 
@@ -8876,7 +8904,6 @@ basic_json_parser_63:
             return *result;
         }
 
-      private:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
@@ -8890,7 +8917,7 @@ basic_json_parser_63:
         @return The string @a s where all occurrences of @a f are replaced
                 with @a t.
 
-        @pre The search string @f must not be empty.
+        @pre The search string @a f must not be empty.
         */
         static void replace_substring(std::string& s,
                                       const std::string& f,
@@ -8932,7 +8959,7 @@ basic_json_parser_63:
                 // we can stop if start == string::npos+1 = 0
                 start != 0;
                 // set the beginning of the next reference token
-                // (could be 0 if slash == std::string::npos)
+                // (will eventually be 0 if slash == std::string::npos)
                 start = slash + 1,
                 // find next slash
                 slash = reference_string.find_first_of("/", start))
@@ -8962,7 +8989,7 @@ basic_json_parser_63:
                 // then transform any occurrence of the sequence '~0' to '~'
                 replace_substring(reference_token, "~0", "~");
 
-                // store the reference token
+                // finally, store the reference token
                 reference_tokens.push_back(reference_token);
             }
         }
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 8364d03b..dd4eeb83 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -198,6 +198,9 @@ class basic_json
           AllocatorType>;
 
   public:
+    // forward declarations
+    template<typename Base> class json_reverse_iterator;
+    class json_pointer;
 
     /////////////////////
     // container types //
@@ -227,9 +230,6 @@ class basic_json
     /// the type of an element const pointer
     using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
 
-    // forward declaration
-    template<typename Base> class json_reverse_iterator;
-
     /// an iterator for a basic_json container
     class iterator;
     /// a const iterator for a basic_json container
@@ -3595,6 +3595,28 @@ class basic_json
         }
     }
 
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a reference to the element at with specified JSON pointer @a ptr.
+
+    @param p  JSON pointer to the desired element
+
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer& ptr)
+    {
+        return ptr.get(*this);
+    }
+
+    /*!
+    @copydoc basic_json::operator[](const json_pointer&)
+    */
+    const_reference operator[](const json_pointer& ptr) const
+    {
+        return ptr.get(*this);
+    }
+
     /*!
     @brief access specified object element with default value
 
@@ -8125,6 +8147,11 @@ class basic_json
     };
 
   public:
+    /*!
+    @brief JSON Pointer
+
+    @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+    */
     class json_pointer
     {
       public:
@@ -8132,13 +8159,14 @@ class basic_json
         json_pointer() = default;
 
         /// nonempty reference token
-        json_pointer(const std::string& s)
+        explicit json_pointer(const std::string& s)
         {
             split(s);
         }
 
+      private:
         /// return referenced value
-        reference get(reference j)
+        reference get(reference j) const
         {
             pointer result = &j;
 
@@ -8186,7 +8214,6 @@ class basic_json
             return *result;
         }
 
-      private:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
@@ -8200,7 +8227,7 @@ class basic_json
         @return The string @a s where all occurrences of @a f are replaced
                 with @a t.
 
-        @pre The search string @f must not be empty.
+        @pre The search string @a f must not be empty.
         */
         static void replace_substring(std::string& s,
                                       const std::string& f,
@@ -8242,7 +8269,7 @@ class basic_json
                 // we can stop if start == string::npos+1 = 0
                 start != 0;
                 // set the beginning of the next reference token
-                // (could be 0 if slash == std::string::npos)
+                // (will eventually be 0 if slash == std::string::npos)
                 start = slash + 1,
                 // find next slash
                 slash = reference_string.find_first_of("/", start))
@@ -8272,7 +8299,7 @@ class basic_json
                 // then transform any occurrence of the sequence '~0' to '~'
                 replace_substring(reference_token, "~0", "~");
 
-                // store the reference token
+                // finally, store the reference token
                 reference_tokens.push_back(reference_token);
             }
         }
diff --git a/test/unit.cpp b/test/unit.cpp
index 628c2755..223de2c2 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12078,11 +12078,17 @@ TEST_CASE("JSON pointers")
             // the whole document
             CHECK(json::json_pointer().get(j) == j);
             CHECK(json::json_pointer("").get(j) == j);
+            CHECK(j[json::json_pointer()] == j);
+            CHECK(j[json::json_pointer("")] == j);
 
             // array access
             CHECK(json::json_pointer("/foo").get(j) == j["foo"]);
             CHECK(json::json_pointer("/foo/0").get(j) == j["foo"][0]);
             CHECK(json::json_pointer("/foo/1").get(j) == j["foo"][1]);
+            CHECK(j[json::json_pointer("/foo")] == j["foo"]);
+            CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
+            CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
+            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
 
             // empty string access
             CHECK(json::json_pointer("/").get(j) == j[""]);
@@ -12108,15 +12114,35 @@ TEST_CASE("JSON pointers")
 
         SECTION("const access")
         {
-            CHECK(j_const == json::json_pointer().get(j_const));
-            CHECK(j_const == json::json_pointer("").get(j_const));
+            // the whole document
+            CHECK(json::json_pointer().get(j_const) == j_const);
+            CHECK(json::json_pointer("").get(j_const) == j_const);
 
-            CHECK(j_const["foo"] == json::json_pointer("/foo").get(j_const));
-            CHECK(j_const["foo"][0] == json::json_pointer("/foo/0").get(j_const));
-            CHECK(j_const["foo"][1] == json::json_pointer("/foo/1").get(j_const));
+            // array access
+            CHECK(json::json_pointer("/foo").get(j_const) == j_const["foo"]);
+            CHECK(json::json_pointer("/foo/0").get(j_const) == j_const["foo"][0]);
+            CHECK(json::json_pointer("/foo/1").get(j_const) == j_const["foo"][1]);
 
-            CHECK(j_const[""] == json::json_pointer("/").get(j_const));
-            CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const));
+            // empty string access
+            CHECK(json::json_pointer("/").get(j_const) == j_const[""]);
+
+            // other cases
+            CHECK(json::json_pointer("/ ").get(j_const) == j_const[" "]);
+            CHECK(json::json_pointer("/c%d").get(j_const) == j_const["c%d"]);
+            CHECK(json::json_pointer("/e^f").get(j_const) == j_const["e^f"]);
+            CHECK(json::json_pointer("/g|h").get(j_const) == j_const["g|h"]);
+            CHECK(json::json_pointer("/i\\j").get(j_const) == j_const["i\\j"]);
+            CHECK(json::json_pointer("/k\"l").get(j_const) == j_const["k\"l"]);
+
+            // escaped access
+            CHECK(json::json_pointer("/a~1b").get(j_const) == j_const["a/b"]);
+            CHECK(json::json_pointer("/m~0n").get(j_const) == j_const["m~n"]);
+
+            // unescaped access
+            CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
+            CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
+            // "/a/b" works for JSON {"a": {"b": 42}}
+            CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
         }
 
         SECTION("user-defined string literal")

From 007359675b0ee39f5a70ce1a7adfc9d268388050 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 16 Apr 2016 16:39:20 +0200
Subject: [PATCH 05/13] added a flatten function

---
 src/json.hpp      | 111 +++++++++++++++++++++++++++++++++++-----------
 src/json.hpp.re2c | 111 +++++++++++++++++++++++++++++++++++-----------
 test/unit.cpp     |  47 ++++++++++++++++++++
 3 files changed, 217 insertions(+), 52 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 2d383136..6aca24ad 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8907,32 +8907,6 @@ basic_json_parser_63:
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /*!
-        @brief replace all occurrences of a substring by another string
-
-        @param[in,out] s  the string to manipulate
-        @param[in]     f  the substring to replace with @a t
-        @param[out]    t  the string to replace @a f
-
-        @return The string @a s where all occurrences of @a f are replaced
-                with @a t.
-
-        @pre The search string @a f must not be empty.
-        */
-        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
-            );
-        }
-
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8993,7 +8967,92 @@ basic_json_parser_63:
                 reference_tokens.push_back(reference_token);
             }
         }
+
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @a f must not be empty.
+
+        @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
+            );
+        }
+
+        /*!
+        @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
+        */
+        static void flatten(const std::string reference_string,
+                            const basic_json& value,
+                            basic_json& result)
+        {
+            switch (value.m_type)
+            {
+                case value_t::array:
+                {
+                    // 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:
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto& element : *value.m_value.object)
+                    {
+                        // escape "~"" to "~0" and "/" to "~1"
+                        std::string key(element.first);
+                        replace_substring(key, "~", "~0");
+                        replace_substring(key, "/", "~1");
+
+                        flatten(reference_string + "/" + key,
+                                element.second, result);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // add primitive value with its reference string
+                    result[reference_string] = value;
+                    break;
+                }
+            }
+        }
     };
+
+    /*!
+    @return an object that maps JSON pointers to primitve values
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index dd4eeb83..74827e2c 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8217,32 +8217,6 @@ class basic_json
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
 
-        /*!
-        @brief replace all occurrences of a substring by another string
-
-        @param[in,out] s  the string to manipulate
-        @param[in]     f  the substring to replace with @a t
-        @param[out]    t  the string to replace @a f
-
-        @return The string @a s where all occurrences of @a f are replaced
-                with @a t.
-
-        @pre The search string @a f must not be empty.
-        */
-        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
-            );
-        }
-
         /// split the string input to reference tokens
         void split(std::string reference_string)
         {
@@ -8303,7 +8277,92 @@ class basic_json
                 reference_tokens.push_back(reference_token);
             }
         }
+
+        /*!
+        @brief replace all occurrences of a substring by another string
+
+        @param[in,out] s  the string to manipulate
+        @param[in]     f  the substring to replace with @a t
+        @param[out]    t  the string to replace @a f
+
+        @return The string @a s where all occurrences of @a f are replaced
+                with @a t.
+
+        @pre The search string @a f must not be empty.
+
+        @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
+            );
+        }
+
+        /*!
+        @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
+        */
+        static void flatten(const std::string reference_string,
+                            const basic_json& value,
+                            basic_json& result)
+        {
+            switch (value.m_type)
+            {
+                case value_t::array:
+                {
+                    // 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:
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto& element : *value.m_value.object)
+                    {
+                        // escape "~"" to "~0" and "/" to "~1"
+                        std::string key(element.first);
+                        replace_substring(key, "~", "~0");
+                        replace_substring(key, "/", "~1");
+
+                        flatten(reference_string + "/" + key,
+                                element.second, result);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // add primitive value with its reference string
+                    result[reference_string] = value;
+                    break;
+                }
+            }
+        }
     };
+
+    /*!
+    @return an object that maps JSON pointers to primitve values
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index 223de2c2..0d9aa0a7 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12168,6 +12168,53 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
         }
     }
+
+    SECTION("flatten")
+    {
+        json j =
+        {
+            {"pi", 3.141},
+            {"happy", true},
+            {"name", "Niels"},
+            {"nothing", nullptr},
+            {
+                "answer", {
+                    {"everything", 42}
+                }
+            },
+            {"list", {1, 0, 2}},
+            {
+                "object", {
+                    {"currency", "USD"},
+                    {"value", 42.99},
+                    {"", "empty string"},
+                    {"/", "slash"},
+                    {"~", "tilde"},
+                    {"~1", "tilde1"}
+                }
+            }
+        };
+
+        json j_flatten =
+        {
+            {"/pi", 3.141},
+            {"/happy", true},
+            {"/name", "Niels"},
+            {"/nothing", nullptr},
+            {"/answer/everything", 42},
+            {"/list/0", 1},
+            {"/list/1", 0},
+            {"/list/2", 2},
+            {"/object/currency", "USD"},
+            {"/object/value", 42.99},
+            {"/object/", "empty string"},
+            {"/object/~1", "slash"},
+            {"/object/~0", "tilde"},
+            {"/object/~01", "tilde1"}
+        };
+
+        CHECK(j.flatten() == j_flatten);
+    }
 }
 
 TEST_CASE("regression tests")

From f834965b44572b14a0865763aed3fde02facf0e4 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 16 Apr 2016 20:45:40 +0200
Subject: [PATCH 06/13] implemented deflatten function

---
 src/json.hpp      | 81 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/json.hpp.re2c | 81 ++++++++++++++++++++++++++++++++++++++++++++++-
 test/unit.cpp     | 17 ++++++++++
 3 files changed, 177 insertions(+), 2 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 6aca24ad..96bc1f32 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8855,7 +8855,6 @@ basic_json_parser_63:
         }
 
       private:
-        /// return referenced value
         reference get(reference j) const
         {
             pointer result = &j;
@@ -8880,6 +8879,49 @@ basic_json_parser_63:
             return *result;
         }
 
+        reference get2(reference j) const
+        {
+            pointer result = &j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result->m_type)
+                {
+                    case value_t::null:
+                    {
+                        if (reference_token == "0")
+                        {
+                            result = &result->operator[](0);
+                        }
+                        else
+                        {
+                            result = &result->operator[](reference_token);
+                        }
+                        continue;
+                    }
+
+                    case value_t::object:
+                    {
+                        result = &result->operator[](reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *result;
+        }
+
         const_reference get(const_reference j) const
         {
             const_pointer result = &j;
@@ -9042,6 +9084,35 @@ basic_json_parser_63:
                 }
             }
         }
+
+        /*!
+        @param[in] value  flattened JSON
+
+        @return deflattened JSON
+        */
+        static basic_json deflatten(const basic_json& value)
+        {
+            if (not value.is_object())
+            {
+                throw std::domain_error("only objects can be deflattened");
+            }
+
+            basic_json result;
+
+            // iterate the JSON object values
+            for (const auto& element : *value.m_value.object)
+            {
+                if (not element.second.is_primitive())
+                {
+                    throw std::domain_error("values in object must be primitive");
+                }
+
+                // assign value to reference pointed to by JSON pointer
+                json_pointer(element.first).get2(result) = element.second;
+            }
+
+            return result;
+        }
     };
 
     /*!
@@ -9053,6 +9124,14 @@ basic_json_parser_63:
         json_pointer::flatten("", *this, result);
         return result;
     }
+
+    /*!
+    @return the original JSON from a flattened version
+    */
+    basic_json deflatten() const
+    {
+        return json_pointer::deflatten(*this);
+    }
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 74827e2c..1a049cd5 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8165,7 +8165,6 @@ class basic_json
         }
 
       private:
-        /// return referenced value
         reference get(reference j) const
         {
             pointer result = &j;
@@ -8190,6 +8189,49 @@ class basic_json
             return *result;
         }
 
+        reference get2(reference j) const
+        {
+            pointer result = &j;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (result->m_type)
+                {
+                    case value_t::null:
+                    {
+                        if (reference_token == "0")
+                        {
+                            result = &result->operator[](0);
+                        }
+                        else
+                        {
+                            result = &result->operator[](reference_token);
+                        }
+                        continue;
+                    }
+
+                    case value_t::object:
+                    {
+                        result = &result->operator[](reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *result;
+        }
+
         const_reference get(const_reference j) const
         {
             const_pointer result = &j;
@@ -8352,6 +8394,35 @@ class basic_json
                 }
             }
         }
+
+        /*!
+        @param[in] value  flattened JSON
+
+        @return deflattened JSON
+        */
+        static basic_json deflatten(const basic_json& value)
+        {
+            if (not value.is_object())
+            {
+                throw std::domain_error("only objects can be deflattened");
+            }
+
+            basic_json result;
+
+            // iterate the JSON object values
+            for (const auto& element : *value.m_value.object)
+            {
+                if (not element.second.is_primitive())
+                {
+                    throw std::domain_error("values in object must be primitive");
+                }
+
+                // assign value to reference pointed to by JSON pointer
+                json_pointer(element.first).get2(result) = element.second;
+            }
+
+            return result;
+        }
     };
 
     /*!
@@ -8363,6 +8434,14 @@ class basic_json
         json_pointer::flatten("", *this, result);
         return result;
     }
+
+    /*!
+    @return the original JSON from a flattened version
+    */
+    basic_json deflatten() const
+    {
+        return json_pointer::deflatten(*this);
+    }
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index 0d9aa0a7..1ace40d0 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12213,7 +12213,24 @@ TEST_CASE("JSON pointers")
             {"/object/~01", "tilde1"}
         };
 
+        // check if flattened result is as expected
         CHECK(j.flatten() == j_flatten);
+
+        // check if deflattened result is as expected
+        CHECK(j_flatten.deflatten() == j);
+
+        // explicit roundtrip check
+        CHECK(j.flatten().deflatten() == j);
+
+        // roundtrip for primitive values
+        json j_null;
+        CHECK(j_null.flatten().deflatten() == j_null);
+        json j_number = 42;
+        CHECK(j_number.flatten().deflatten() == j_number);
+        json j_boolean = false;
+        CHECK(j_boolean.flatten().deflatten() == j_boolean);
+        json j_string = "foo";
+        CHECK(j_string.flatten().deflatten() == j_string);
     }
 }
 

From 40e899a819ac105a591ab1e8405b938728c94e2e Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 17:39:35 +0200
Subject: [PATCH 07/13] cleanup and documentation

---
 doc/examples/flatten.cpp                      |  34 ++
 doc/examples/flatten.link                     |   1 +
 doc/examples/flatten.output                   |  16 +
 doc/examples/operatorjson_pointer.cpp         |  47 +++
 doc/examples/operatorjson_pointer.link        |   1 +
 doc/examples/operatorjson_pointer.output      |   8 +
 doc/examples/operatorjson_pointer_const.cpp   |  23 ++
 doc/examples/operatorjson_pointer_const.link  |   1 +
 .../operatorjson_pointer_const.output         |   4 +
 doc/examples/unflatten.cpp                    |  28 ++
 doc/examples/unflatten.link                   |   1 +
 doc/examples/unflatten.output                 |  22 +
 src/json.hpp                                  | 386 +++++++++++++++---
 src/json.hpp.re2c                             | 386 +++++++++++++++---
 test/unit.cpp                                 | 222 ++++++----
 15 files changed, 978 insertions(+), 202 deletions(-)
 create mode 100644 doc/examples/flatten.cpp
 create mode 100644 doc/examples/flatten.link
 create mode 100644 doc/examples/flatten.output
 create mode 100644 doc/examples/operatorjson_pointer.cpp
 create mode 100644 doc/examples/operatorjson_pointer.link
 create mode 100644 doc/examples/operatorjson_pointer.output
 create mode 100644 doc/examples/operatorjson_pointer_const.cpp
 create mode 100644 doc/examples/operatorjson_pointer_const.link
 create mode 100644 doc/examples/operatorjson_pointer_const.output
 create mode 100644 doc/examples/unflatten.cpp
 create mode 100644 doc/examples/unflatten.link
 create mode 100644 doc/examples/unflatten.output

diff --git a/doc/examples/flatten.cpp b/doc/examples/flatten.cpp
new file mode 100644
index 00000000..5d769202
--- /dev/null
+++ b/doc/examples/flatten.cpp
@@ -0,0 +1,34 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create JSON value
+    json j =
+    {
+        {"pi", 3.141},
+        {"happy", true},
+        {"name", "Niels"},
+        {"nothing", nullptr},
+        {
+            "answer", {
+                {"everything", 42}
+            }
+        },
+        {"list", {1, 0, 2}},
+        {
+            "object", {
+                {"currency", "USD"},
+                {"value", 42.99},
+                {"", "empty string"},
+                {"/", "slash"},
+                {"~", "tilde"},
+                {"~1", "tilde1"}
+            }
+        }
+    };
+
+    // call flatten()
+    std::cout << std::setw(4) << j.flatten() << '\n';
+}
diff --git a/doc/examples/flatten.link b/doc/examples/flatten.link
new file mode 100644
index 00000000..70ba78ba
--- /dev/null
+++ b/doc/examples/flatten.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/kODXfzcksgstdBRD"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/flatten.output b/doc/examples/flatten.output
new file mode 100644
index 00000000..beb368fa
--- /dev/null
+++ b/doc/examples/flatten.output
@@ -0,0 +1,16 @@
+{
+    "/answer/everything": 42,
+    "/happy": true,
+    "/list/0": 1,
+    "/list/1": 0,
+    "/list/2": 2,
+    "/name": "Niels",
+    "/nothing": null,
+    "/object/": "empty string",
+    "/object/currency": "USD",
+    "/object/value": 42.99,
+    "/object/~0": "tilde",
+    "/object/~01": "tilde1",
+    "/object/~1": "slash",
+    "/pi": 3.141
+}
diff --git a/doc/examples/operatorjson_pointer.cpp b/doc/examples/operatorjson_pointer.cpp
new file mode 100644
index 00000000..18e41c1f
--- /dev/null
+++ b/doc/examples/operatorjson_pointer.cpp
@@ -0,0 +1,47 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create a JSON value
+    json j =
+    {
+        {"number", 1}, {"string", "foo"}, {"array", {1, 2}}
+    };
+
+    // read-only access
+
+    // output element with JSON pointer "/number"
+    std::cout << j["/number"_json_pointer] << '\n';
+    // output element with JSON pointer "/string"
+    std::cout << j["/string"_json_pointer] << '\n';
+    // output element with JSON pointer "/array"
+    std::cout << j["/array"_json_pointer] << '\n';
+    // output element with JSON pointer "/array/1"
+    std::cout << j["/array/1"_json_pointer] << '\n';
+
+    // writing access
+
+    // change the string
+    j["/string"_json_pointer] = "bar";
+    // output the changed string
+    std::cout << j["string"] << '\n';
+
+    // "change" a nonexisting object entry
+    j["/boolean"_json_pointer] = true;
+    // output the changed object
+    std::cout << j << '\n';
+
+    // change an array element
+    j["/array/1"_json_pointer] = 21;
+    // "change" an array element with nonexisting index
+    j["/array/4"_json_pointer] = 44;
+    // output the changed array
+    std::cout << j["array"] << '\n';
+
+    // "change" the arry element past the end
+    j["/array/-"_json_pointer] = 55;
+    // output the changed array
+    std::cout << j["array"] << '\n';
+}
diff --git a/doc/examples/operatorjson_pointer.link b/doc/examples/operatorjson_pointer.link
new file mode 100644
index 00000000..3cee69e7
--- /dev/null
+++ b/doc/examples/operatorjson_pointer.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/6oeNnra3wjPijLSr"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/operatorjson_pointer.output b/doc/examples/operatorjson_pointer.output
new file mode 100644
index 00000000..1fd1b032
--- /dev/null
+++ b/doc/examples/operatorjson_pointer.output
@@ -0,0 +1,8 @@
+1
+"foo"
+[1,2]
+2
+"bar"
+{"array":[1,2],"boolean":true,"number":1,"string":"bar"}
+[1,21,null,null,44]
+[1,21,null,null,44,55]
diff --git a/doc/examples/operatorjson_pointer_const.cpp b/doc/examples/operatorjson_pointer_const.cpp
new file mode 100644
index 00000000..20ac36cb
--- /dev/null
+++ b/doc/examples/operatorjson_pointer_const.cpp
@@ -0,0 +1,23 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create a JSON value
+    const json j =
+    {
+        {"number", 1}, {"string", "foo"}, {"array", {1, 2}}
+    };
+
+    // read-only access
+
+    // output element with JSON pointer "/number"
+    std::cout << j["/number"_json_pointer] << '\n';
+    // output element with JSON pointer "/string"
+    std::cout << j["/string"_json_pointer] << '\n';
+    // output element with JSON pointer "/array"
+    std::cout << j["/array"_json_pointer] << '\n';
+    // output element with JSON pointer "/array/1"
+    std::cout << j["/array/1"_json_pointer] << '\n';
+}
diff --git a/doc/examples/operatorjson_pointer_const.link b/doc/examples/operatorjson_pointer_const.link
new file mode 100644
index 00000000..b13a9b19
--- /dev/null
+++ b/doc/examples/operatorjson_pointer_const.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/YmjwNAhsoeMXw5Ve"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/operatorjson_pointer_const.output b/doc/examples/operatorjson_pointer_const.output
new file mode 100644
index 00000000..7b9306bb
--- /dev/null
+++ b/doc/examples/operatorjson_pointer_const.output
@@ -0,0 +1,4 @@
+1
+"foo"
+[1,2]
+2
diff --git a/doc/examples/unflatten.cpp b/doc/examples/unflatten.cpp
new file mode 100644
index 00000000..39c674c9
--- /dev/null
+++ b/doc/examples/unflatten.cpp
@@ -0,0 +1,28 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create JSON value
+    json j_flattened =
+    {
+        {"/answer/everything", 42},
+        {"/happy", true},
+        {"/list/0", 1},
+        {"/list/1", 0},
+        {"/list/2", 2},
+        {"/name", "Niels"},
+        {"/nothing", nullptr},
+        {"/object/", "empty string"},
+        {"/object/currency", "USD"},
+        {"/object/value", 42.99},
+        {"/object/~0", "tilde"},
+        {"/object/~01", "tilde1"},
+        {"/object/~1", "slash"},
+        {"/pi", 3.141}
+    };
+
+    // call unflatten()
+    std::cout << std::setw(4) << j_flattened.unflatten() << '\n';
+}
diff --git a/doc/examples/unflatten.link b/doc/examples/unflatten.link
new file mode 100644
index 00000000..bc7594a0
--- /dev/null
+++ b/doc/examples/unflatten.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/ITqCZsXmi0I7KGYy"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/unflatten.output b/doc/examples/unflatten.output
new file mode 100644
index 00000000..f57c9c9a
--- /dev/null
+++ b/doc/examples/unflatten.output
@@ -0,0 +1,22 @@
+{
+    "answer": {
+        "everything": 42
+    },
+    "happy": true,
+    "list": [
+        1,
+        0,
+        2
+    ],
+    "name": "Niels",
+    "nothing": null,
+    "object": {
+        "": "empty string",
+        "/": "slash",
+        "currency": "USD",
+        "value": 42.99,
+        "~": "tilde",
+        "~1": "tilde1"
+    },
+    "pi": 3.141
+}
diff --git a/src/json.hpp b/src/json.hpp
index 96bc1f32..6cf369bd 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -3598,23 +3598,86 @@ class basic_json
     /*!
     @brief access specified element via JSON Pointer
 
-    Returns a reference to the element at with specified JSON pointer @a ptr.
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. Similar to
+    @ref operator[](const typename object_t::key_type&), `null` values
+    are created in arrays and objects if necessary.
 
-    @param p  JSON pointer to the desired element
+    In particular:
+    - If the JSON pointer points to an object key that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned.
+    - If the JSON pointer points to an array index that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned. All indices between the current maximum and the given
+      index are also filled with `null`.
+    - The special value `-` is treated as a synonym for the index past the
+      end.
+
+    @param[in] ptr  a JSON pointer
+
+    @return reference to the JSON value pointed to by @a ptr
+
+    @complexity Linear in the length of the JSON pointer.
+
+    @throw std::out_of_range  if the JSON pointer can not be resolved
+
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer}
 
     @since version 2.0.0
     */
     reference operator[](const json_pointer& ptr)
     {
-        return ptr.get(*this);
+        return ptr.get_unchecked(this);
     }
 
     /*!
-    @copydoc basic_json::operator[](const json_pointer&)
+    @brief access specified element via JSON Pointer
+
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. The function does not change the JSON
+    value; no `null` values are created. In particular, the the special value
+    `-` yields an exception.
+
+    @param[in] ptr  a JSON pointer
+
+    @return reference to the JSON value pointed to by @a ptr
+
+    @complexity Linear in the length of the JSON pointer.
+
+    @throw std::out_of_range  if the JSON pointer can not be resolved
+    @throw std::out_of_range  if the special value `-` is used for an array
+
+    @liveexample{The behavior is shown in the example.,
+    operatorjson_pointer_const}
+
+    @since version 2.0.0
     */
     const_reference operator[](const json_pointer& ptr) const
     {
-        return ptr.get(*this);
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a reference to the element at with specified JSON pointer @a ptr.
+
+    @param ptr  JSON pointer to the desired element
+
+    @since version 2.0.0
+    */
+    reference at(const json_pointer& ptr)
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @copydoc basic_json::at(const json_pointer&)
+    */
+    const_reference at(const json_pointer& ptr) const
+    {
+        return ptr.get_checked(this);
     }
 
     /*!
@@ -8841,45 +8904,28 @@ basic_json_parser_63:
     @brief JSON Pointer
 
     @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:
         /// empty reference token
         json_pointer() = default;
 
         /// nonempty reference token
         explicit json_pointer(const std::string& s)
-        {
-            split(s);
-        }
+            : reference_tokens(split(s))
+        {}
 
       private:
-        reference get(reference j) const
-        {
-            pointer result = &j;
-
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (result->m_type)
-                {
-                    case value_t::object:
-                        result = &result->at(reference_token);
-                        continue;
-
-                    case value_t::array:
-                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
-
-                    default:
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
-                }
-            }
-
-            return *result;
-        }
-
-        reference get2(reference j) const
+        /*!
+        @brief create and return a reference to the pointed to value
+        */
+        reference get_and_create(reference j) const
         {
             pointer result = &j;
 
@@ -8922,40 +8968,172 @@ basic_json_parser_63:
             return *result;
         }
 
-        const_reference get(const_reference j) const
-        {
-            const_pointer result = &j;
+        /*!
+        @brief return a reference to the pointed to value
 
+        @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 std::out_of_range  if the JSON pointer can not be resolved
+        */
+        reference get_unchecked(pointer ptr) const
+        {
             for (const auto& reference_token : reference_tokens)
             {
-                switch (result->m_type)
+                switch (ptr->m_type)
                 {
                     case value_t::object:
-                        result = &result->at(reference_token);
-                        continue;
+                    {
+                        ptr = &ptr->operator[](reference_token);
+                        break;
+                    }
 
                     case value_t::array:
-                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                    {
+                        if (reference_token == "-")
+                        {
+                            ptr = &ptr->operator[](ptr->m_value.array->size());
+                        }
+                        else
+                        {
+                            ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        }
+                        break;
+                    }
 
                     default:
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
                 }
             }
 
-            return *result;
+            return *ptr;
         }
 
-        /// the reference tokens
-        std::vector<std::string> reference_tokens {};
+        reference get_checked(pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->at(reference_token);
+                        break;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("cannot resolve reference token '-'");
+                        }
+                        else
+                        {
+                            ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        }
+                        break;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("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
+        */
+        const_reference get_unchecked(const_pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->operator[](reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
+                        }
+                        ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *ptr;
+        }
+
+        const_reference get_checked(const_pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->at(reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
+                        }
+                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *ptr;
+        }
 
         /// split the string input to reference tokens
-        void split(std::string reference_string)
+        std::vector<std::string> split(std::string reference_string)
         {
+            std::vector<std::string> result;
+
             // special case: empty reference string -> no reference tokens
             if (reference_string.empty())
             {
-                return;
+                return result;
             }
 
             // check if nonempty reference string begins with slash
@@ -9006,10 +9184,13 @@ basic_json_parser_63:
                 replace_substring(reference_token, "~0", "~");
 
                 // finally, store the reference token
-                reference_tokens.push_back(reference_token);
+                result.push_back(reference_token);
             }
+
+            return result;
         }
 
+      private:
         /*!
         @brief replace all occurrences of a substring by another string
 
@@ -9042,6 +9223,8 @@ basic_json_parser_63:
         @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,
@@ -9051,27 +9234,43 @@ basic_json_parser_63:
             {
                 case value_t::array:
                 {
-                    // iterate array and use index as reference string
-                    for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                    if (value.m_value.array->empty())
                     {
-                        flatten(reference_string + "/" + std::to_string(i),
-                                value.m_value.array->operator[](i), result);
+                        // 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:
                 {
-                    // iterate object and use keys as reference string
-                    for (const auto& element : *value.m_value.object)
+                    if (value.m_value.object->empty())
                     {
-                        // escape "~"" to "~0" and "/" to "~1"
-                        std::string key(element.first);
-                        replace_substring(key, "~", "~0");
-                        replace_substring(key, "/", "~1");
+                        // 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)
+                        {
+                            // escape "~"" to "~0" and "/" to "~1"
+                            std::string key(element.first);
+                            replace_substring(key, "~", "~0");
+                            replace_substring(key, "/", "~1");
 
-                        flatten(reference_string + "/" + key,
-                                element.second, result);
+                            flatten(reference_string + "/" + key,
+                                    element.second, result);
+                        }
                     }
                     break;
                 }
@@ -9088,13 +9287,13 @@ basic_json_parser_63:
         /*!
         @param[in] value  flattened JSON
 
-        @return deflattened JSON
+        @return unflattened JSON
         */
-        static basic_json deflatten(const basic_json& value)
+        static basic_json unflatten(const basic_json& value)
         {
             if (not value.is_object())
             {
-                throw std::domain_error("only objects can be deflattened");
+                throw std::domain_error("only objects can be unflattened");
             }
 
             basic_json result;
@@ -9108,15 +9307,44 @@ basic_json_parser_63:
                 }
 
                 // assign value to reference pointed to by JSON pointer
-                json_pointer(element.first).get2(result) = element.second;
+                json_pointer(element.first).get_and_create(result) = element.second;
             }
 
             return result;
         }
+
+      private:
+        /// the reference tokens
+        const std::vector<std::string> reference_tokens {};
     };
 
+    ////////////////////////////
+    // JSON Pointer functions //
+    ////////////////////////////
+
+    /// @name JSON Pointer functions
+    /// @{
+
     /*!
+    @brief return flattened JSON value
+
+    The function creates a JSON object whose keys are JSON pointers (see
+    [RFC 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
+    primitive. The original JSON value can be restored using the
+    @ref unflatten() function.
+
     @return an object that maps JSON pointers to primitve values
+
+    @note Empty objects and arrays are flattened to `null`.
+
+    @complexity Linear in the size the JSON value.
+
+    @liveexample{The following code shows how a JSON object is flattened to an
+    object whose keys consist of JSON pointers.,flatten}
+
+    @sa @ref unflatten() for the reverse function
+
+    @since version 2.0.0
     */
     basic_json flatten() const
     {
@@ -9126,12 +9354,38 @@ basic_json_parser_63:
     }
 
     /*!
+    @brief unflatten a previously flattened JSON value
+
+    The function restores the arbitrary nesting of a JSON value that has been
+    flattened before using the @ref flatten() function. The JSON value must
+    meet certain constraints:
+    1. The value must be an object.
+    2. The keys must be JSON pointers (see
+       [RFC 6901](https://tools.ietf.org/html/rfc6901))
+    3. The mapped values must be primitive JSON types.
+
     @return the original JSON from a flattened version
+
+    @note Empty objects and arrays are flattened by @ref flatten() to `null`
+          values and can not unflattened to their original type. Apart from
+          this example, for a JSON value `j`, the following is always true:
+          `j == j.flatten().unflatten()`.
+
+    @complexity Linear in the size the JSON value.
+
+    @liveexample{The following code shows how a flattened JSON object is
+    unflattened into the original nested JSON object.,unflatten}
+
+    @sa @ref flatten() for the reverse function
+
+    @since version 2.0.0
     */
-    basic_json deflatten() const
+    basic_json unflatten() const
     {
-        return json_pointer::deflatten(*this);
+        return json_pointer::unflatten(*this);
     }
+
+    /// @}
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 1a049cd5..7fe9673f 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -3598,23 +3598,86 @@ class basic_json
     /*!
     @brief access specified element via JSON Pointer
 
-    Returns a reference to the element at with specified JSON pointer @a ptr.
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. Similar to
+    @ref operator[](const typename object_t::key_type&), `null` values
+    are created in arrays and objects if necessary.
 
-    @param p  JSON pointer to the desired element
+    In particular:
+    - If the JSON pointer points to an object key that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned.
+    - If the JSON pointer points to an array index that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned. All indices between the current maximum and the given
+      index are also filled with `null`.
+    - The special value `-` is treated as a synonym for the index past the
+      end.
+
+    @param[in] ptr  a JSON pointer
+
+    @return reference to the JSON value pointed to by @a ptr
+
+    @complexity Linear in the length of the JSON pointer.
+
+    @throw std::out_of_range  if the JSON pointer can not be resolved
+
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer}
 
     @since version 2.0.0
     */
     reference operator[](const json_pointer& ptr)
     {
-        return ptr.get(*this);
+        return ptr.get_unchecked(this);
     }
 
     /*!
-    @copydoc basic_json::operator[](const json_pointer&)
+    @brief access specified element via JSON Pointer
+
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. The function does not change the JSON
+    value; no `null` values are created. In particular, the the special value
+    `-` yields an exception.
+
+    @param[in] ptr  a JSON pointer
+
+    @return reference to the JSON value pointed to by @a ptr
+
+    @complexity Linear in the length of the JSON pointer.
+
+    @throw std::out_of_range  if the JSON pointer can not be resolved
+    @throw std::out_of_range  if the special value `-` is used for an array
+
+    @liveexample{The behavior is shown in the example.,
+    operatorjson_pointer_const}
+
+    @since version 2.0.0
     */
     const_reference operator[](const json_pointer& ptr) const
     {
-        return ptr.get(*this);
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a reference to the element at with specified JSON pointer @a ptr.
+
+    @param ptr  JSON pointer to the desired element
+
+    @since version 2.0.0
+    */
+    reference at(const json_pointer& ptr)
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @copydoc basic_json::at(const json_pointer&)
+    */
+    const_reference at(const json_pointer& ptr) const
+    {
+        return ptr.get_checked(this);
     }
 
     /*!
@@ -8151,45 +8214,28 @@ class basic_json
     @brief JSON Pointer
 
     @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:
         /// empty reference token
         json_pointer() = default;
 
         /// nonempty reference token
         explicit json_pointer(const std::string& s)
-        {
-            split(s);
-        }
+            : reference_tokens(split(s))
+        {}
 
       private:
-        reference get(reference j) const
-        {
-            pointer result = &j;
-
-            for (const auto& reference_token : reference_tokens)
-            {
-                switch (result->m_type)
-                {
-                    case value_t::object:
-                        result = &result->at(reference_token);
-                        continue;
-
-                    case value_t::array:
-                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
-
-                    default:
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
-                }
-            }
-
-            return *result;
-        }
-
-        reference get2(reference j) const
+        /*!
+        @brief create and return a reference to the pointed to value
+        */
+        reference get_and_create(reference j) const
         {
             pointer result = &j;
 
@@ -8232,40 +8278,172 @@ class basic_json
             return *result;
         }
 
-        const_reference get(const_reference j) const
-        {
-            const_pointer result = &j;
+        /*!
+        @brief return a reference to the pointed to value
 
+        @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 std::out_of_range  if the JSON pointer can not be resolved
+        */
+        reference get_unchecked(pointer ptr) const
+        {
             for (const auto& reference_token : reference_tokens)
             {
-                switch (result->m_type)
+                switch (ptr->m_type)
                 {
                     case value_t::object:
-                        result = &result->at(reference_token);
-                        continue;
+                    {
+                        ptr = &ptr->operator[](reference_token);
+                        break;
+                    }
 
                     case value_t::array:
-                        result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                    {
+                        if (reference_token == "-")
+                        {
+                            ptr = &ptr->operator[](ptr->m_value.array->size());
+                        }
+                        else
+                        {
+                            ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        }
+                        break;
+                    }
 
                     default:
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
                 }
             }
 
-            return *result;
+            return *ptr;
         }
 
-        /// the reference tokens
-        std::vector<std::string> reference_tokens {};
+        reference get_checked(pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->at(reference_token);
+                        break;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("cannot resolve reference token '-'");
+                        }
+                        else
+                        {
+                            ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        }
+                        break;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("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
+        */
+        const_reference get_unchecked(const_pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->operator[](reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
+                        }
+                        ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *ptr;
+        }
+
+        const_reference get_checked(const_pointer ptr) const
+        {
+            for (const auto& reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                    case value_t::object:
+                    {
+                        ptr = &ptr->at(reference_token);
+                        continue;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (reference_token == "-")
+                        {
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
+                        }
+                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        continue;
+                    }
+
+                    default:
+                    {
+                        throw std::out_of_range("unresolved reference token '" + reference_token + "'");
+                    }
+                }
+            }
+
+            return *ptr;
+        }
 
         /// split the string input to reference tokens
-        void split(std::string reference_string)
+        std::vector<std::string> split(std::string reference_string)
         {
+            std::vector<std::string> result;
+
             // special case: empty reference string -> no reference tokens
             if (reference_string.empty())
             {
-                return;
+                return result;
             }
 
             // check if nonempty reference string begins with slash
@@ -8316,10 +8494,13 @@ class basic_json
                 replace_substring(reference_token, "~0", "~");
 
                 // finally, store the reference token
-                reference_tokens.push_back(reference_token);
+                result.push_back(reference_token);
             }
+
+            return result;
         }
 
+      private:
         /*!
         @brief replace all occurrences of a substring by another string
 
@@ -8352,6 +8533,8 @@ class basic_json
         @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,
@@ -8361,27 +8544,43 @@ class basic_json
             {
                 case value_t::array:
                 {
-                    // iterate array and use index as reference string
-                    for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                    if (value.m_value.array->empty())
                     {
-                        flatten(reference_string + "/" + std::to_string(i),
-                                value.m_value.array->operator[](i), result);
+                        // 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:
                 {
-                    // iterate object and use keys as reference string
-                    for (const auto& element : *value.m_value.object)
+                    if (value.m_value.object->empty())
                     {
-                        // escape "~"" to "~0" and "/" to "~1"
-                        std::string key(element.first);
-                        replace_substring(key, "~", "~0");
-                        replace_substring(key, "/", "~1");
+                        // 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)
+                        {
+                            // escape "~"" to "~0" and "/" to "~1"
+                            std::string key(element.first);
+                            replace_substring(key, "~", "~0");
+                            replace_substring(key, "/", "~1");
 
-                        flatten(reference_string + "/" + key,
-                                element.second, result);
+                            flatten(reference_string + "/" + key,
+                                    element.second, result);
+                        }
                     }
                     break;
                 }
@@ -8398,13 +8597,13 @@ class basic_json
         /*!
         @param[in] value  flattened JSON
 
-        @return deflattened JSON
+        @return unflattened JSON
         */
-        static basic_json deflatten(const basic_json& value)
+        static basic_json unflatten(const basic_json& value)
         {
             if (not value.is_object())
             {
-                throw std::domain_error("only objects can be deflattened");
+                throw std::domain_error("only objects can be unflattened");
             }
 
             basic_json result;
@@ -8418,15 +8617,44 @@ class basic_json
                 }
 
                 // assign value to reference pointed to by JSON pointer
-                json_pointer(element.first).get2(result) = element.second;
+                json_pointer(element.first).get_and_create(result) = element.second;
             }
 
             return result;
         }
+
+      private:
+        /// the reference tokens
+        const std::vector<std::string> reference_tokens {};
     };
 
+    ////////////////////////////
+    // JSON Pointer functions //
+    ////////////////////////////
+
+    /// @name JSON Pointer functions
+    /// @{
+
     /*!
+    @brief return flattened JSON value
+
+    The function creates a JSON object whose keys are JSON pointers (see
+    [RFC 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
+    primitive. The original JSON value can be restored using the
+    @ref unflatten() function.
+
     @return an object that maps JSON pointers to primitve values
+
+    @note Empty objects and arrays are flattened to `null`.
+
+    @complexity Linear in the size the JSON value.
+
+    @liveexample{The following code shows how a JSON object is flattened to an
+    object whose keys consist of JSON pointers.,flatten}
+
+    @sa @ref unflatten() for the reverse function
+
+    @since version 2.0.0
     */
     basic_json flatten() const
     {
@@ -8436,12 +8664,38 @@ class basic_json
     }
 
     /*!
+    @brief unflatten a previously flattened JSON value
+
+    The function restores the arbitrary nesting of a JSON value that has been
+    flattened before using the @ref flatten() function. The JSON value must
+    meet certain constraints:
+    1. The value must be an object.
+    2. The keys must be JSON pointers (see
+       [RFC 6901](https://tools.ietf.org/html/rfc6901))
+    3. The mapped values must be primitive JSON types.
+
     @return the original JSON from a flattened version
+
+    @note Empty objects and arrays are flattened by @ref flatten() to `null`
+          values and can not unflattened to their original type. Apart from
+          this example, for a JSON value `j`, the following is always true:
+          `j == j.flatten().unflatten()`.
+
+    @complexity Linear in the size the JSON value.
+
+    @liveexample{The following code shows how a flattened JSON object is
+    unflattened into the original nested JSON object.,unflatten}
+
+    @sa @ref flatten() for the reverse function
+
+    @since version 2.0.0
     */
-    basic_json deflatten() const
+    basic_json unflatten() const
     {
-        return json_pointer::deflatten(*this);
+        return json_pointer::unflatten(*this);
     }
+
+    /// @}
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index 1ace40d0..a3b9035d 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12054,119 +12054,195 @@ TEST_CASE("Unicode", "[hide]")
 
 TEST_CASE("JSON pointers")
 {
+    SECTION("errors")
+    {
+        CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
+        CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
+
+        CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
+        CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
+
+        CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
+        CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
+    }
+
     SECTION("examples from RFC 6901")
     {
-        json j = R"(
-        {
-            "foo": ["bar", "baz"],
-            "": 0,
-            "a/b": 1,
-            "c%d": 2,
-            "e^f": 3,
-            "g|h": 4,
-            "i\\j": 5,
-            "k\"l": 6,
-            " ": 7,
-            "m~n": 8
-        }
-        )"_json;
-
-        const json j_const = j;
-
         SECTION("nonconst access")
         {
+            json j = R"(
+            {
+                "foo": ["bar", "baz"],
+                "": 0,
+                "a/b": 1,
+                "c%d": 2,
+                "e^f": 3,
+                "g|h": 4,
+                "i\\j": 5,
+                "k\"l": 6,
+                " ": 7,
+                "m~n": 8
+            }
+            )"_json;
+
             // the whole document
-            CHECK(json::json_pointer().get(j) == j);
-            CHECK(json::json_pointer("").get(j) == j);
             CHECK(j[json::json_pointer()] == j);
             CHECK(j[json::json_pointer("")] == j);
 
             // array access
-            CHECK(json::json_pointer("/foo").get(j) == j["foo"]);
-            CHECK(json::json_pointer("/foo/0").get(j) == j["foo"][0]);
-            CHECK(json::json_pointer("/foo/1").get(j) == j["foo"][1]);
             CHECK(j[json::json_pointer("/foo")] == j["foo"]);
             CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
 
             // empty string access
-            CHECK(json::json_pointer("/").get(j) == j[""]);
+            CHECK(j[json::json_pointer("/")] == j[""]);
 
             // other cases
-            CHECK(json::json_pointer("/ ").get(j) == j[" "]);
-            CHECK(json::json_pointer("/c%d").get(j) == j["c%d"]);
-            CHECK(json::json_pointer("/e^f").get(j) == j["e^f"]);
-            CHECK(json::json_pointer("/g|h").get(j) == j["g|h"]);
-            CHECK(json::json_pointer("/i\\j").get(j) == j["i\\j"]);
-            CHECK(json::json_pointer("/k\"l").get(j) == j["k\"l"]);
+            CHECK(j[json::json_pointer("/ ")] == j[" "]);
+            CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
+            CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
+            CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
+            CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
+            CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
 
             // escaped access
-            CHECK(json::json_pointer("/a~1b").get(j) == j["a/b"]);
-            CHECK(json::json_pointer("/m~0n").get(j) == j["m~n"]);
+            CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
+            CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
 
             // unescaped access
-            CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
-            CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
+            CHECK_THROWS_AS(j[json::json_pointer("/a/b")], std::out_of_range);
+            CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'");
             // "/a/b" works for JSON {"a": {"b": 42}}
-            CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
+            CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
         }
 
         SECTION("const access")
         {
+            const json j = R"(
+            {
+                "foo": ["bar", "baz"],
+                "": 0,
+                "a/b": 1,
+                "c%d": 2,
+                "e^f": 3,
+                "g|h": 4,
+                "i\\j": 5,
+                "k\"l": 6,
+                " ": 7,
+                "m~n": 8
+            }
+            )"_json;
+
             // the whole document
-            CHECK(json::json_pointer().get(j_const) == j_const);
-            CHECK(json::json_pointer("").get(j_const) == j_const);
+            CHECK(j[json::json_pointer()] == j);
+            CHECK(j[json::json_pointer("")] == j);
 
             // array access
-            CHECK(json::json_pointer("/foo").get(j_const) == j_const["foo"]);
-            CHECK(json::json_pointer("/foo/0").get(j_const) == j_const["foo"][0]);
-            CHECK(json::json_pointer("/foo/1").get(j_const) == j_const["foo"][1]);
+            CHECK(j[json::json_pointer("/foo")] == j["foo"]);
+            CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
+            CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
+            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
 
             // empty string access
-            CHECK(json::json_pointer("/").get(j_const) == j_const[""]);
+            CHECK(j[json::json_pointer("/")] == j[""]);
 
             // other cases
-            CHECK(json::json_pointer("/ ").get(j_const) == j_const[" "]);
-            CHECK(json::json_pointer("/c%d").get(j_const) == j_const["c%d"]);
-            CHECK(json::json_pointer("/e^f").get(j_const) == j_const["e^f"]);
-            CHECK(json::json_pointer("/g|h").get(j_const) == j_const["g|h"]);
-            CHECK(json::json_pointer("/i\\j").get(j_const) == j_const["i\\j"]);
-            CHECK(json::json_pointer("/k\"l").get(j_const) == j_const["k\"l"]);
+            CHECK(j[json::json_pointer("/ ")] == j[" "]);
+            CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
+            CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
+            CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
+            CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
+            CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
 
             // escaped access
-            CHECK(json::json_pointer("/a~1b").get(j_const) == j_const["a/b"]);
-            CHECK(json::json_pointer("/m~0n").get(j_const) == j_const["m~n"]);
+            CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
+            CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
 
             // unescaped access
-            CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
-            CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
-            // "/a/b" works for JSON {"a": {"b": 42}}
-            CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
+            CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range);
+            CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found");
         }
 
         SECTION("user-defined string literal")
         {
+            json j = R"(
+            {
+                "foo": ["bar", "baz"],
+                "": 0,
+                "a/b": 1,
+                "c%d": 2,
+                "e^f": 3,
+                "g|h": 4,
+                "i\\j": 5,
+                "k\"l": 6,
+                " ": 7,
+                "m~n": 8
+            }
+            )"_json;
+
             // the whole document
-            CHECK(""_json_pointer.get(j) == j);
+            CHECK(j[""_json_pointer] == j);
 
             // array access
-            CHECK("/foo"_json_pointer.get(j) == j["foo"]);
-            CHECK("/foo/0"_json_pointer.get(j) == j["foo"][0]);
-            CHECK("/foo/1"_json_pointer.get(j) == j["foo"][1]);
+            CHECK(j["/foo"_json_pointer] == j["foo"]);
+            CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
+            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
         }
+    }
 
-        SECTION("errors")
+    SECTION("array access")
+    {
+        SECTION("nonconst access")
         {
-            CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
-            CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
+            json j = {1, 2, 3};
 
-            CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
-            CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
+            // check reading access
+            CHECK(j["/0"_json_pointer] == j[0]);
+            CHECK(j["/1"_json_pointer] == j[1]);
+            CHECK(j["/2"_json_pointer] == j[2]);
 
-            CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
-            CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
+            // assign to existing index
+            j["/1"_json_pointer] = 13;
+            CHECK(j[1] == json(13));
+
+            // assign to nonexisting index
+            j["/3"_json_pointer] = 33;
+            CHECK(j[3] == json(33));
+
+            // assign to nonexisting index (with gap)
+            j["/5"_json_pointer] = 55;
+            CHECK(j == json({1, 13, 3, 33, nullptr, 55}));
+
+            // assign to "-"
+            j["/-"_json_pointer] = 99;
+            CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
         }
+
+        SECTION("const access")
+        {
+            const json j = {1, 2, 3};
+
+            // check reading access
+            CHECK(j["/0"_json_pointer] == j[0]);
+            CHECK(j["/1"_json_pointer] == j[1]);
+            CHECK(j["/2"_json_pointer] == j[2]);
+
+            // assign to nonexisting index
+            CHECK_THROWS_AS(j.at("/3"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j.at("/3"_json_pointer), "array index 3 is out of range");
+
+            // assign to nonexisting index (with gap)
+            CHECK_THROWS_AS(j.at("/5"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j.at("/5"_json_pointer), "array index 5 is out of range");
+
+            // assign to "-"
+            CHECK_THROWS_AS(j["/-"_json_pointer], std::out_of_range);
+            CHECK_THROWS_WITH(j["/-"_json_pointer], "array index '-' (3) is out of range");
+            CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (3) is out of range");
+        }
+
     }
 
     SECTION("flatten")
@@ -12216,21 +12292,27 @@ TEST_CASE("JSON pointers")
         // check if flattened result is as expected
         CHECK(j.flatten() == j_flatten);
 
-        // check if deflattened result is as expected
-        CHECK(j_flatten.deflatten() == j);
+        // check if unflattened result is as expected
+        CHECK(j_flatten.unflatten() == j);
 
         // explicit roundtrip check
-        CHECK(j.flatten().deflatten() == j);
+        CHECK(j.flatten().unflatten() == j);
 
         // roundtrip for primitive values
         json j_null;
-        CHECK(j_null.flatten().deflatten() == j_null);
+        CHECK(j_null.flatten().unflatten() == j_null);
         json j_number = 42;
-        CHECK(j_number.flatten().deflatten() == j_number);
+        CHECK(j_number.flatten().unflatten() == j_number);
         json j_boolean = false;
-        CHECK(j_boolean.flatten().deflatten() == j_boolean);
+        CHECK(j_boolean.flatten().unflatten() == j_boolean);
         json j_string = "foo";
-        CHECK(j_string.flatten().deflatten() == j_string);
+        CHECK(j_string.flatten().unflatten() == j_string);
+
+        // roundtrip for empty structured values (will be unflattened to null)
+        json j_array(json::value_t::array);
+        CHECK(j_array.flatten().unflatten() == json());
+        json j_object(json::value_t::object);
+        CHECK(j_object.flatten().unflatten() == json());
     }
 }
 

From f883a04c87dd3c3ccd3828172dfe9160376cda67 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 18:18:49 +0200
Subject: [PATCH 08/13] more documentation

---
 doc/examples/json_pointer.cpp    | 46 ++++++++++++++++++++++++++++++++
 doc/examples/json_pointer.link   |  1 +
 doc/examples/json_pointer.output |  3 +++
 src/json.hpp                     | 37 +++++++++++++++++--------
 src/json.hpp.re2c                | 37 +++++++++++++++++--------
 5 files changed, 102 insertions(+), 22 deletions(-)
 create mode 100644 doc/examples/json_pointer.cpp
 create mode 100644 doc/examples/json_pointer.link
 create mode 100644 doc/examples/json_pointer.output

diff --git a/doc/examples/json_pointer.cpp b/doc/examples/json_pointer.cpp
new file mode 100644
index 00000000..140eac3b
--- /dev/null
+++ b/doc/examples/json_pointer.cpp
@@ -0,0 +1,46 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // correct JSON pointers
+    json::json_pointer p1;
+    json::json_pointer p2("");
+    json::json_pointer p3("/");
+    json::json_pointer p4("//");
+    json::json_pointer p5("/foo/bar");
+    json::json_pointer p6("/foo/bar/-");
+    json::json_pointer p7("/foo/~0");
+    json::json_pointer p8("/foo/~1");
+
+    // error: JSON pointer does not begin with a slash
+    try
+    {
+        json::json_pointer p9("foo");
+    }
+    catch (std::domain_error& e)
+    {
+        std::cout << "domain_error: " << e.what() << '\n';
+    }
+
+    // error: JSON pointer uses escape symbol ~ not followed by 0 or 1
+    try
+    {
+        json::json_pointer p10("/foo/~");
+    }
+    catch (std::domain_error& e)
+    {
+        std::cout << "domain_error: " << e.what() << '\n';
+    }
+
+    // error: JSON pointer uses escape symbol ~ not followed by 0 or 1
+    try
+    {
+        json::json_pointer p11("/foo/~3");
+    }
+    catch (std::domain_error& e)
+    {
+        std::cout << "domain_error: " << e.what() << '\n';
+    }
+}
diff --git a/doc/examples/json_pointer.link b/doc/examples/json_pointer.link
new file mode 100644
index 00000000..c10c5fb9
--- /dev/null
+++ b/doc/examples/json_pointer.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/9OR6xQ1rRZDA0W94"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/json_pointer.output b/doc/examples/json_pointer.output
new file mode 100644
index 00000000..b81c8a20
--- /dev/null
+++ b/doc/examples/json_pointer.output
@@ -0,0 +1,3 @@
+domain_error: JSON pointer must be empty or begin with '/'
+domain_error: escape error: '~' must be followed with '0' or '1'
+domain_error: escape error: '~' must be followed with '0' or '1'
diff --git a/src/json.hpp b/src/json.hpp
index 6cf369bd..c40e004a 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8913,11 +8913,26 @@ basic_json_parser_63:
         friend class basic_json;
 
       public:
-        /// empty reference token
-        json_pointer() = default;
+        /*!
+        @brief create JSON pointer
 
-        /// nonempty reference token
-        explicit json_pointer(const std::string& s)
+        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 std::domain_error  if reference token is nonempty and does not
+               begin with a slash (`/`), or if a tilde (`~`) is not followed
+               by `0` (representing `~`) or `1` (representing `/`).
+
+        @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))
         {}
 
@@ -8943,19 +8958,19 @@ basic_json_parser_63:
                         {
                             result = &result->operator[](reference_token);
                         }
-                        continue;
+                        break;
                     }
 
                     case value_t::object:
                     {
                         result = &result->operator[](reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
                     {
                         result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:
@@ -9066,7 +9081,7 @@ basic_json_parser_63:
                     case value_t::object:
                     {
                         ptr = &ptr->operator[](reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
@@ -9078,7 +9093,7 @@ basic_json_parser_63:
                                                     ") is out of range");
                         }
                         ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:
@@ -9100,7 +9115,7 @@ basic_json_parser_63:
                     case value_t::object:
                     {
                         ptr = &ptr->at(reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
@@ -9112,7 +9127,7 @@ basic_json_parser_63:
                                                     ") is out of range");
                         }
                         ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 7fe9673f..e2ea78b6 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8223,11 +8223,26 @@ class basic_json
         friend class basic_json;
 
       public:
-        /// empty reference token
-        json_pointer() = default;
+        /*!
+        @brief create JSON pointer
 
-        /// nonempty reference token
-        explicit json_pointer(const std::string& s)
+        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 std::domain_error  if reference token is nonempty and does not
+               begin with a slash (`/`), or if a tilde (`~`) is not followed
+               by `0` (representing `~`) or `1` (representing `/`).
+
+        @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))
         {}
 
@@ -8253,19 +8268,19 @@ class basic_json
                         {
                             result = &result->operator[](reference_token);
                         }
-                        continue;
+                        break;
                     }
 
                     case value_t::object:
                     {
                         result = &result->operator[](reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
                     {
                         result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:
@@ -8376,7 +8391,7 @@ class basic_json
                     case value_t::object:
                     {
                         ptr = &ptr->operator[](reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
@@ -8388,7 +8403,7 @@ class basic_json
                                                     ") is out of range");
                         }
                         ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:
@@ -8410,7 +8425,7 @@ class basic_json
                     case value_t::object:
                     {
                         ptr = &ptr->at(reference_token);
-                        continue;
+                        break;
                     }
 
                     case value_t::array:
@@ -8422,7 +8437,7 @@ class basic_json
                                                     ") is out of range");
                         }
                         ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
-                        continue;
+                        break;
                     }
 
                     default:

From 0835eb293ffa80bab9591fe55656d57d76805f40 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 18:54:54 +0200
Subject: [PATCH 09/13] improved RFC compliance and code coverage

---
 src/json.hpp      | 48 +++++++++++++++++++++++++++++++++++++++++++----
 src/json.hpp.re2c | 48 +++++++++++++++++++++++++++++++++++++++++++----
 test/unit.cpp     | 24 ++++++++++++++++++++++++
 3 files changed, 112 insertions(+), 8 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index c40e004a..47046c03 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8992,7 +8992,9 @@ basic_json_parser_63:
 
         @complexity Linear in the length of the JSON pointer.
 
-        @throw std::out_of_range  if the JSON pointer can not be resolved
+        @throw std::out_of_range      if the JSON pointer can not be resolved
+        @throw std::domain_error      if an array index begins with '0'
+        @throw std::invalid_argument  if an array index was not a number
         */
         reference get_unchecked(pointer ptr) const
         {
@@ -9002,18 +9004,27 @@ basic_json_parser_63:
                 {
                     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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
                         if (reference_token == "-")
                         {
+                            // explicityly treat "-" as index beyond the end
                             ptr = &ptr->operator[](ptr->m_value.array->size());
                         }
                         else
                         {
+                            // convert array index to number; unchecked access
                             ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         }
                         break;
@@ -9037,6 +9048,7 @@ basic_json_parser_63:
                 {
                     case value_t::object:
                     {
+                        // note: at performs range check
                         ptr = &ptr->at(reference_token);
                         break;
                     }
@@ -9045,12 +9057,20 @@ basic_json_parser_63:
                     {
                         if (reference_token == "-")
                         {
-                            throw std::out_of_range("cannot resolve reference token '-'");
+                            // "-" always fails the range check
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
                         }
-                        else
+
+                        // error condition (cf. RFC 6901, Sect. 4)
+                        if (reference_token.size() > 1 and reference_token[0] == '0')
                         {
-                            ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                            throw std::domain_error("array index must not begin with '0'");
                         }
+
+                        // note: at performs range check
+                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -9080,6 +9100,7 @@ basic_json_parser_63:
                 {
                     case value_t::object:
                     {
+                        // use unchecked object access
                         ptr = &ptr->operator[](reference_token);
                         break;
                     }
@@ -9088,10 +9109,19 @@ basic_json_parser_63:
                     {
                         if (reference_token == "-")
                         {
+                            // "-" cannot be used for const access
                             throw std::out_of_range("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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
+                        // use unchecked array access
                         ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
@@ -9114,6 +9144,7 @@ basic_json_parser_63:
                 {
                     case value_t::object:
                     {
+                        // note: at performs range check
                         ptr = &ptr->at(reference_token);
                         break;
                     }
@@ -9122,10 +9153,19 @@ basic_json_parser_63:
                     {
                         if (reference_token == "-")
                         {
+                            // "-" always fails the range check
                             throw std::out_of_range("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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
+                        // note: at performs range check
                         ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index e2ea78b6..ac11f08a 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8302,7 +8302,9 @@ class basic_json
 
         @complexity Linear in the length of the JSON pointer.
 
-        @throw std::out_of_range  if the JSON pointer can not be resolved
+        @throw std::out_of_range      if the JSON pointer can not be resolved
+        @throw std::domain_error      if an array index begins with '0'
+        @throw std::invalid_argument  if an array index was not a number
         */
         reference get_unchecked(pointer ptr) const
         {
@@ -8312,18 +8314,27 @@ class basic_json
                 {
                     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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
                         if (reference_token == "-")
                         {
+                            // explicityly treat "-" as index beyond the end
                             ptr = &ptr->operator[](ptr->m_value.array->size());
                         }
                         else
                         {
+                            // convert array index to number; unchecked access
                             ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         }
                         break;
@@ -8347,6 +8358,7 @@ class basic_json
                 {
                     case value_t::object:
                     {
+                        // note: at performs range check
                         ptr = &ptr->at(reference_token);
                         break;
                     }
@@ -8355,12 +8367,20 @@ class basic_json
                     {
                         if (reference_token == "-")
                         {
-                            throw std::out_of_range("cannot resolve reference token '-'");
+                            // "-" always fails the range check
+                            throw std::out_of_range("array index '-' (" +
+                                                    std::to_string(ptr->m_value.array->size()) +
+                                                    ") is out of range");
                         }
-                        else
+
+                        // error condition (cf. RFC 6901, Sect. 4)
+                        if (reference_token.size() > 1 and reference_token[0] == '0')
                         {
-                            ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                            throw std::domain_error("array index must not begin with '0'");
                         }
+
+                        // note: at performs range check
+                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -8390,6 +8410,7 @@ class basic_json
                 {
                     case value_t::object:
                     {
+                        // use unchecked object access
                         ptr = &ptr->operator[](reference_token);
                         break;
                     }
@@ -8398,10 +8419,19 @@ class basic_json
                     {
                         if (reference_token == "-")
                         {
+                            // "-" cannot be used for const access
                             throw std::out_of_range("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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
+                        // use unchecked array access
                         ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
@@ -8424,6 +8454,7 @@ class basic_json
                 {
                     case value_t::object:
                     {
+                        // note: at performs range check
                         ptr = &ptr->at(reference_token);
                         break;
                     }
@@ -8432,10 +8463,19 @@ class basic_json
                     {
                         if (reference_token == "-")
                         {
+                            // "-" always fails the range check
                             throw std::out_of_range("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')
+                        {
+                            throw std::domain_error("array index must not begin with '0'");
+                        }
+
+                        // note: at performs range check
                         ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
diff --git a/test/unit.cpp b/test/unit.cpp
index a3b9035d..f4d162e1 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12196,6 +12196,7 @@ TEST_CASE("JSON pointers")
         SECTION("nonconst access")
         {
             json j = {1, 2, 3};
+            const json j_const = j;
 
             // check reading access
             CHECK(j["/0"_json_pointer] == j[0]);
@@ -12214,9 +12215,32 @@ TEST_CASE("JSON pointers")
             j["/5"_json_pointer] = 55;
             CHECK(j == json({1, 13, 3, 33, nullptr, 55}));
 
+            // error with leading 0
+            CHECK_THROWS_AS(j["/01"_json_pointer], std::domain_error);
+            CHECK_THROWS_WITH(j["/01"_json_pointer], "array index must not begin with '0'");
+            CHECK_THROWS_AS(j_const["/01"_json_pointer], std::domain_error);
+            CHECK_THROWS_WITH(j_const["/01"_json_pointer], "array index must not begin with '0'");
+            CHECK_THROWS_AS(j.at("/01"_json_pointer), std::domain_error);
+            CHECK_THROWS_WITH(j.at("/01"_json_pointer), "array index must not begin with '0'");
+            CHECK_THROWS_AS(j_const.at("/01"_json_pointer), std::domain_error);
+            CHECK_THROWS_WITH(j_const.at("/01"_json_pointer), "array index must not begin with '0'");
+
+            // error with incorrect numbers
+            CHECK_THROWS_AS(j["/one"_json_pointer] = 1, std::invalid_argument);
+
             // assign to "-"
             j["/-"_json_pointer] = 99;
             CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
+
+            // error when using "-" in const object
+            CHECK_THROWS_AS(j_const["/-"_json_pointer], std::out_of_range);
+            CHECK_THROWS_WITH(j_const["/-"_json_pointer], "array index '-' (3) is out of range");
+
+            // error when using "-" with at
+            CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (7) is out of range");
+            CHECK_THROWS_AS(j_const.at("/-"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j_const.at("/-"_json_pointer), "array index '-' (3) is out of range");
         }
 
         SECTION("const access")

From 7034ae2486fa58a2cf31bfc86c42f4c07a6e7b23 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 19:12:12 +0200
Subject: [PATCH 10/13] improved test coverage

---
 test/unit.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/test/unit.cpp b/test/unit.cpp
index f4d162e1..f79a29fd 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12095,6 +12095,10 @@ TEST_CASE("JSON pointers")
             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
 
+            // checked array access
+            CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
+            CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
+
             // empty string access
             CHECK(j[json::json_pointer("/")] == j[""]);
 
@@ -12106,6 +12110,14 @@ TEST_CASE("JSON pointers")
             CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
             CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
 
+            // checked access
+            CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
+            CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
+            CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
+            CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
+            CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
+            CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
+
             // escaped access
             CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
             CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
@@ -12115,6 +12127,13 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'");
             // "/a/b" works for JSON {"a": {"b": 42}}
             CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
+
+            // unresolved access
+            json j_primitive = 1;
+            CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
+            CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
+            CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
         }
 
         SECTION("const access")
@@ -12144,6 +12163,10 @@ TEST_CASE("JSON pointers")
             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
 
+            // checked array access
+            CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
+            CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
+
             // empty string access
             CHECK(j[json::json_pointer("/")] == j[""]);
 
@@ -12155,6 +12178,14 @@ TEST_CASE("JSON pointers")
             CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
             CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
 
+            // checked access
+            CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
+            CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
+            CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
+            CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
+            CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
+            CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
+
             // escaped access
             CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
             CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
@@ -12162,6 +12193,13 @@ TEST_CASE("JSON pointers")
             // unescaped access
             CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range);
             CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found");
+
+            // unresolved access
+            const json j_primitive = 1;
+            CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
+            CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
+            CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
+            CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
         }
 
         SECTION("user-defined string literal")
@@ -12319,6 +12357,14 @@ TEST_CASE("JSON pointers")
         // check if unflattened result is as expected
         CHECK(j_flatten.unflatten() == j);
 
+        // error for nonobjects
+        CHECK_THROWS_AS(json(1).unflatten(), std::domain_error);
+        CHECK_THROWS_WITH(json(1).unflatten(), "only objects can be unflattened");
+
+        // error for nonprimitve values
+        CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error);
+        CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive");
+
         // explicit roundtrip check
         CHECK(j.flatten().unflatten() == j);
 

From 6268287940ce3a2523fda959ed1efe7d5f0051c4 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 22:08:21 +0200
Subject: [PATCH 11/13] improved documentation and test coverage

---
 README.md                   |  2 +-
 doc/examples/flatten.cpp    |  3 ++
 doc/examples/flatten.link   |  2 +-
 doc/examples/flatten.output |  1 +
 src/json.hpp                | 58 ++++++++++++++++++++++++++++++-------
 src/json.hpp.re2c           | 58 ++++++++++++++++++++++++++++++-------
 test/unit.cpp               |  5 ++++
 7 files changed, 105 insertions(+), 24 deletions(-)

diff --git a/README.md b/README.md
index d39663ee..2dd60bf1 100644
--- a/README.md
+++ b/README.md
@@ -428,7 +428,7 @@ $ make
 $ ./json_unit "*"
 
 ===============================================================================
-All tests passed (3344299 assertions in 29 test cases)
+All tests passed (3344416 assertions in 30 test cases)
 ```
 
 For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml).
diff --git a/doc/examples/flatten.cpp b/doc/examples/flatten.cpp
index 5d769202..0601f8a3 100644
--- a/doc/examples/flatten.cpp
+++ b/doc/examples/flatten.cpp
@@ -31,4 +31,7 @@ int main()
 
     // call flatten()
     std::cout << std::setw(4) << j.flatten() << '\n';
+
+    // flatten for a primitive value
+    std::cout << j["pi"].flatten() << '\n';
 }
diff --git a/doc/examples/flatten.link b/doc/examples/flatten.link
index 70ba78ba..0fe78bbb 100644
--- a/doc/examples/flatten.link
+++ b/doc/examples/flatten.link
@@ -1 +1 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/kODXfzcksgstdBRD"><b>online</b></a>
\ No newline at end of file
+<a target="_blank" href="http://melpon.org/wandbox/permlink/skGi8b32VhI8HOgV"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/flatten.output b/doc/examples/flatten.output
index beb368fa..fedfc8ef 100644
--- a/doc/examples/flatten.output
+++ b/doc/examples/flatten.output
@@ -14,3 +14,4 @@
     "/object/~1": "slash",
     "/pi": 3.141
 }
+{"":3.141}
diff --git a/src/json.hpp b/src/json.hpp
index 47046c03..030c8f2c 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -3273,8 +3273,8 @@ class basic_json
 
     @return reference to the element at index @a idx
 
-    @throw std::domain_error if JSON is not an array or null; example: `"cannot
-    use operator[] with string"`
+    @throw std::domain_error if JSON is not an array or null; example:
+    `"cannot use operator[] with string"`
 
     @complexity Constant if @a idx is in the range of the array. Otherwise
     linear in `idx - size()`.
@@ -3620,7 +3620,9 @@ class basic_json
 
     @complexity Linear in the length of the JSON pointer.
 
-    @throw std::out_of_range  if the JSON pointer can not be resolved
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
 
     @liveexample{The behavior is shown in the example.,operatorjson_pointer}
 
@@ -3645,8 +3647,9 @@ class basic_json
 
     @complexity Linear in the length of the JSON pointer.
 
-    @throw std::out_of_range  if the JSON pointer can not be resolved
-    @throw std::out_of_range  if the special value `-` is used for an array
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
 
     @liveexample{The behavior is shown in the example.,
     operatorjson_pointer_const}
@@ -8923,9 +8926,12 @@ basic_json_parser_63:
                       empty string is assumed which references the whole JSON
                       value
 
-        @throw std::domain_error  if reference token is nonempty and does not
-               begin with a slash (`/`), or if a tilde (`~`) is not followed
-               by `0` (representing `~`) or `1` (representing `/`).
+        @throw std::domain_error if reference token is nonempty and does not
+        begin with a slash (`/`); example: `"JSON pointer must be empty or
+        begin with /"`
+        @throw std::domain_error if a tilde (`~`) is not followed by `0`
+        (representing `~`) or `1` (representing `/`); example: `"escape error:
+        ~ must be followed with 0 or 1"`
 
         @liveexample{The example shows the construction several valid JSON
         pointers as well as the exceptional behavior.,json_pointer}
@@ -8944,6 +8950,8 @@ basic_json_parser_63:
         {
             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)
@@ -8952,10 +8960,12 @@ basic_json_parser_63:
                     {
                         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;
@@ -8963,19 +8973,38 @@ basic_json_parser_63:
 
                     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
                         result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
 
+                    /*
+                    This function is only to be called from the unflatten()
+                    function. There, j is initially of type null.
+
+                    - In case the reference tokens are empty, a reference to
+                      j is returned and overwritten by the desired value by
+                      the unflatten() function.
+                    - If there are reference tokens, the null value of j will
+                      be changed to an object or array after reading the first
+                      reference token.
+                    - All subsequent tokens work on arrays or objects and will
+                      not change the type of j.
+
+                    Consequently, the type of @a j will always be null,
+                    object, or array. Hence, the following line is
+                    unreachable.
+                    */
                     default:
                     {
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                        break; // LCOV_EXCL_LINE
                     }
                 }
             }
@@ -9361,7 +9390,11 @@ basic_json_parser_63:
                     throw std::domain_error("values in object must be primitive");
                 }
 
-                // assign value to reference pointed to by JSON pointer
+                // 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;
             }
 
@@ -9390,7 +9423,8 @@ basic_json_parser_63:
 
     @return an object that maps JSON pointers to primitve values
 
-    @note Empty objects and arrays are flattened to `null`.
+    @note Empty objects and arrays are flattened to `null` and will not be
+          reconstructed correctly by the @ref unflatten() function.
 
     @complexity Linear in the size the JSON value.
 
@@ -9428,6 +9462,8 @@ basic_json_parser_63:
 
     @complexity Linear in the size the JSON value.
 
+    @throws std::domain_error
+
     @liveexample{The following code shows how a flattened JSON object is
     unflattened into the original nested JSON object.,unflatten}
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index ac11f08a..f5fbe65b 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -3273,8 +3273,8 @@ class basic_json
 
     @return reference to the element at index @a idx
 
-    @throw std::domain_error if JSON is not an array or null; example: `"cannot
-    use operator[] with string"`
+    @throw std::domain_error if JSON is not an array or null; example:
+    `"cannot use operator[] with string"`
 
     @complexity Constant if @a idx is in the range of the array. Otherwise
     linear in `idx - size()`.
@@ -3620,7 +3620,9 @@ class basic_json
 
     @complexity Linear in the length of the JSON pointer.
 
-    @throw std::out_of_range  if the JSON pointer can not be resolved
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
 
     @liveexample{The behavior is shown in the example.,operatorjson_pointer}
 
@@ -3645,8 +3647,9 @@ class basic_json
 
     @complexity Linear in the length of the JSON pointer.
 
-    @throw std::out_of_range  if the JSON pointer can not be resolved
-    @throw std::out_of_range  if the special value `-` is used for an array
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
 
     @liveexample{The behavior is shown in the example.,
     operatorjson_pointer_const}
@@ -8233,9 +8236,12 @@ class basic_json
                       empty string is assumed which references the whole JSON
                       value
 
-        @throw std::domain_error  if reference token is nonempty and does not
-               begin with a slash (`/`), or if a tilde (`~`) is not followed
-               by `0` (representing `~`) or `1` (representing `/`).
+        @throw std::domain_error if reference token is nonempty and does not
+        begin with a slash (`/`); example: `"JSON pointer must be empty or
+        begin with /"`
+        @throw std::domain_error if a tilde (`~`) is not followed by `0`
+        (representing `~`) or `1` (representing `/`); example: `"escape error:
+        ~ must be followed with 0 or 1"`
 
         @liveexample{The example shows the construction several valid JSON
         pointers as well as the exceptional behavior.,json_pointer}
@@ -8254,6 +8260,8 @@ class basic_json
         {
             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)
@@ -8262,10 +8270,12 @@ class basic_json
                     {
                         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;
@@ -8273,19 +8283,38 @@ class basic_json
 
                     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
                         result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
                         break;
                     }
 
+                    /*
+                    This function is only to be called from the unflatten()
+                    function. There, j is initially of type null.
+
+                    - In case the reference tokens are empty, a reference to
+                      j is returned and overwritten by the desired value by
+                      the unflatten() function.
+                    - If there are reference tokens, the null value of j will
+                      be changed to an object or array after reading the first
+                      reference token.
+                    - All subsequent tokens work on arrays or objects and will
+                      not change the type of j.
+
+                    Consequently, the type of @a j will always be null,
+                    object, or array. Hence, the following line is
+                    unreachable.
+                    */
                     default:
                     {
-                        throw std::domain_error("unresolved reference token '" + reference_token + "'");
+                        break; // LCOV_EXCL_LINE
                     }
                 }
             }
@@ -8671,7 +8700,11 @@ class basic_json
                     throw std::domain_error("values in object must be primitive");
                 }
 
-                // assign value to reference pointed to by JSON pointer
+                // 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;
             }
 
@@ -8700,7 +8733,8 @@ class basic_json
 
     @return an object that maps JSON pointers to primitve values
 
-    @note Empty objects and arrays are flattened to `null`.
+    @note Empty objects and arrays are flattened to `null` and will not be
+          reconstructed correctly by the @ref unflatten() function.
 
     @complexity Linear in the size the JSON value.
 
@@ -8738,6 +8772,8 @@ class basic_json
 
     @complexity Linear in the size the JSON value.
 
+    @throws std::domain_error
+
     @liveexample{The following code shows how a flattened JSON object is
     unflattened into the original nested JSON object.,unflatten}
 
diff --git a/test/unit.cpp b/test/unit.cpp
index f79a29fd..f5a6fc09 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12365,6 +12365,11 @@ TEST_CASE("JSON pointers")
         CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error);
         CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive");
 
+        // error for conflicting values
+        json j_error = {{"", 42}, {"/foo", 17}};
+        CHECK_THROWS_AS(j_error.unflatten(), std::domain_error);
+        CHECK_THROWS_WITH(j_error.unflatten(), "unresolved reference token 'foo'");
+
         // explicit roundtrip check
         CHECK(j.flatten().unflatten() == j);
 

From 1dee40a9697dd4bc22ed6dd13ad5c928d44ff0f6 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 22:34:39 +0200
Subject: [PATCH 12/13] fixed test case

---
 src/json.hpp      | 24 ++++++------------------
 src/json.hpp.re2c | 24 ++++++------------------
 test/unit.cpp     |  2 +-
 3 files changed, 13 insertions(+), 37 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 030c8f2c..5fdba140 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8986,25 +8986,15 @@ basic_json_parser_63:
                     }
 
                     /*
-                    This function is only to be called from the unflatten()
-                    function. There, j is initially of type null.
-
-                    - In case the reference tokens are empty, a reference to
-                      j is returned and overwritten by the desired value by
-                      the unflatten() function.
-                    - If there are reference tokens, the null value of j will
-                      be changed to an object or array after reading the first
-                      reference token.
-                    - All subsequent tokens work on arrays or objects and will
-                      not change the type of j.
-
-                    Consequently, the type of @a j will always be null,
-                    object, or array. Hence, the following line is
-                    unreachable.
+                    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:
                     {
-                        break; // LCOV_EXCL_LINE
+                        throw std::domain_error("invalid value to unflatten");
                     }
                 }
             }
@@ -9462,8 +9452,6 @@ basic_json_parser_63:
 
     @complexity Linear in the size the JSON value.
 
-    @throws std::domain_error
-
     @liveexample{The following code shows how a flattened JSON object is
     unflattened into the original nested JSON object.,unflatten}
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index f5fbe65b..95a484f8 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8296,25 +8296,15 @@ class basic_json
                     }
 
                     /*
-                    This function is only to be called from the unflatten()
-                    function. There, j is initially of type null.
-
-                    - In case the reference tokens are empty, a reference to
-                      j is returned and overwritten by the desired value by
-                      the unflatten() function.
-                    - If there are reference tokens, the null value of j will
-                      be changed to an object or array after reading the first
-                      reference token.
-                    - All subsequent tokens work on arrays or objects and will
-                      not change the type of j.
-
-                    Consequently, the type of @a j will always be null,
-                    object, or array. Hence, the following line is
-                    unreachable.
+                    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:
                     {
-                        break; // LCOV_EXCL_LINE
+                        throw std::domain_error("invalid value to unflatten");
                     }
                 }
             }
@@ -8772,8 +8762,6 @@ class basic_json
 
     @complexity Linear in the size the JSON value.
 
-    @throws std::domain_error
-
     @liveexample{The following code shows how a flattened JSON object is
     unflattened into the original nested JSON object.,unflatten}
 
diff --git a/test/unit.cpp b/test/unit.cpp
index f5a6fc09..2666e111 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12368,7 +12368,7 @@ TEST_CASE("JSON pointers")
         // error for conflicting values
         json j_error = {{"", 42}, {"/foo", 17}};
         CHECK_THROWS_AS(j_error.unflatten(), std::domain_error);
-        CHECK_THROWS_WITH(j_error.unflatten(), "unresolved reference token 'foo'");
+        CHECK_THROWS_WITH(j_error.unflatten(), "invalid value to unflatten");
 
         // explicit roundtrip check
         CHECK(j.flatten().unflatten() == j);

From 08c97df4207282c280b252609d8d64f0dda80ed9 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 23:18:07 +0200
Subject: [PATCH 13/13] added examples

---
 doc/examples/at_json_pointer.cpp          | 35 +++++++++++++++++
 doc/examples/at_json_pointer.link         |  1 +
 doc/examples/at_json_pointer.output       |  6 +++
 doc/examples/at_json_pointer_const.cpp    | 23 +++++++++++
 doc/examples/at_json_pointer_const.link   |  1 +
 doc/examples/at_json_pointer_const.output |  4 ++
 src/json.hpp                              | 47 ++++++++++++++++++-----
 src/json.hpp.re2c                         | 47 ++++++++++++++++++-----
 8 files changed, 144 insertions(+), 20 deletions(-)
 create mode 100644 doc/examples/at_json_pointer.cpp
 create mode 100644 doc/examples/at_json_pointer.link
 create mode 100644 doc/examples/at_json_pointer.output
 create mode 100644 doc/examples/at_json_pointer_const.cpp
 create mode 100644 doc/examples/at_json_pointer_const.link
 create mode 100644 doc/examples/at_json_pointer_const.output

diff --git a/doc/examples/at_json_pointer.cpp b/doc/examples/at_json_pointer.cpp
new file mode 100644
index 00000000..0665e608
--- /dev/null
+++ b/doc/examples/at_json_pointer.cpp
@@ -0,0 +1,35 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create a JSON value
+    json j =
+    {
+        {"number", 1}, {"string", "foo"}, {"array", {1, 2}}
+    };
+
+    // read-only access
+
+    // output element with JSON pointer "/number"
+    std::cout << j.at("/number"_json_pointer) << '\n';
+    // output element with JSON pointer "/string"
+    std::cout << j.at("/string"_json_pointer) << '\n';
+    // output element with JSON pointer "/array"
+    std::cout << j.at("/array"_json_pointer) << '\n';
+    // output element with JSON pointer "/array/1"
+    std::cout << j.at("/array/1"_json_pointer) << '\n';
+
+    // writing access
+
+    // change the string
+    j.at("/string"_json_pointer) = "bar";
+    // output the changed string
+    std::cout << j["string"] << '\n';
+
+    // change an array element
+    j.at("/array/1"_json_pointer) = 21;
+    // output the changed array
+    std::cout << j["array"] << '\n';
+}
diff --git a/doc/examples/at_json_pointer.link b/doc/examples/at_json_pointer.link
new file mode 100644
index 00000000..5356294e
--- /dev/null
+++ b/doc/examples/at_json_pointer.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/dGjf71qpuaptNhpP"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/at_json_pointer.output b/doc/examples/at_json_pointer.output
new file mode 100644
index 00000000..11913c72
--- /dev/null
+++ b/doc/examples/at_json_pointer.output
@@ -0,0 +1,6 @@
+1
+"foo"
+[1,2]
+2
+"bar"
+[1,21]
diff --git a/doc/examples/at_json_pointer_const.cpp b/doc/examples/at_json_pointer_const.cpp
new file mode 100644
index 00000000..e3cfc515
--- /dev/null
+++ b/doc/examples/at_json_pointer_const.cpp
@@ -0,0 +1,23 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create a JSON value
+    json j =
+    {
+        {"number", 1}, {"string", "foo"}, {"array", {1, 2}}
+    };
+
+    // read-only access
+
+    // output element with JSON pointer "/number"
+    std::cout << j.at("/number"_json_pointer) << '\n';
+    // output element with JSON pointer "/string"
+    std::cout << j.at("/string"_json_pointer) << '\n';
+    // output element with JSON pointer "/array"
+    std::cout << j.at("/array"_json_pointer) << '\n';
+    // output element with JSON pointer "/array/1"
+    std::cout << j.at("/array/1"_json_pointer) << '\n';
+}
diff --git a/doc/examples/at_json_pointer_const.link b/doc/examples/at_json_pointer_const.link
new file mode 100644
index 00000000..905e60d3
--- /dev/null
+++ b/doc/examples/at_json_pointer_const.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/O1Jx8KXGu0EqkwFg"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/at_json_pointer_const.output b/doc/examples/at_json_pointer_const.output
new file mode 100644
index 00000000..7b9306bb
--- /dev/null
+++ b/doc/examples/at_json_pointer_const.output
@@ -0,0 +1,4 @@
+1
+"foo"
+[1,2]
+2
diff --git a/src/json.hpp b/src/json.hpp
index 5fdba140..ffa46067 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -3616,9 +3616,9 @@ class basic_json
 
     @param[in] ptr  a JSON pointer
 
-    @return reference to the JSON value pointed to by @a ptr
+    @return reference to the element pointed to by @a ptr
 
-    @complexity Linear in the length of the JSON pointer.
+    @complexity Constant.
 
     @throw std::out_of_range      if the JSON pointer can not be resolved
     @throw std::domain_error      if an array index begins with '0'
@@ -3641,18 +3641,17 @@ class basic_json
     value; no `null` values are created. In particular, the the special value
     `-` yields an exception.
 
-    @param[in] ptr  a JSON pointer
+    @param[in] ptr  JSON pointer to the desired element
 
-    @return reference to the JSON value pointed to by @a ptr
+    @return const reference to the element pointed to by @a ptr
 
-    @complexity Linear in the length of the JSON pointer.
+    @complexity Constant.
 
     @throw std::out_of_range      if the JSON pointer can not be resolved
     @throw std::domain_error      if an array index begins with '0'
     @throw std::invalid_argument  if an array index was not a number
 
-    @liveexample{The behavior is shown in the example.,
-    operatorjson_pointer_const}
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
 
     @since version 2.0.0
     */
@@ -3664,9 +3663,20 @@ class basic_json
     /*!
     @brief access specified element via JSON Pointer
 
-    Returns a reference to the element at with specified JSON pointer @a ptr.
+    Returns a reference to the element at with specified JSON pointer @a ptr,
+    with bounds checking.
 
-    @param ptr  JSON pointer to the desired element
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer}
 
     @since version 2.0.0
     */
@@ -3676,7 +3686,24 @@ class basic_json
     }
 
     /*!
-    @copydoc basic_json::at(const json_pointer&)
+    @brief access specified element via JSON Pointer
+
+    Returns a const reference to the element at with specified JSON pointer
+    @a ptr, with bounds checking.
+
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer_const}
+
+    @since version 2.0.0
     */
     const_reference at(const json_pointer& ptr) const
     {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 95a484f8..ced7ffba 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -3616,9 +3616,9 @@ class basic_json
 
     @param[in] ptr  a JSON pointer
 
-    @return reference to the JSON value pointed to by @a ptr
+    @return reference to the element pointed to by @a ptr
 
-    @complexity Linear in the length of the JSON pointer.
+    @complexity Constant.
 
     @throw std::out_of_range      if the JSON pointer can not be resolved
     @throw std::domain_error      if an array index begins with '0'
@@ -3641,18 +3641,17 @@ class basic_json
     value; no `null` values are created. In particular, the the special value
     `-` yields an exception.
 
-    @param[in] ptr  a JSON pointer
+    @param[in] ptr  JSON pointer to the desired element
 
-    @return reference to the JSON value pointed to by @a ptr
+    @return const reference to the element pointed to by @a ptr
 
-    @complexity Linear in the length of the JSON pointer.
+    @complexity Constant.
 
     @throw std::out_of_range      if the JSON pointer can not be resolved
     @throw std::domain_error      if an array index begins with '0'
     @throw std::invalid_argument  if an array index was not a number
 
-    @liveexample{The behavior is shown in the example.,
-    operatorjson_pointer_const}
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
 
     @since version 2.0.0
     */
@@ -3664,9 +3663,20 @@ class basic_json
     /*!
     @brief access specified element via JSON Pointer
 
-    Returns a reference to the element at with specified JSON pointer @a ptr.
+    Returns a reference to the element at with specified JSON pointer @a ptr,
+    with bounds checking.
 
-    @param ptr  JSON pointer to the desired element
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer}
 
     @since version 2.0.0
     */
@@ -3676,7 +3686,24 @@ class basic_json
     }
 
     /*!
-    @copydoc basic_json::at(const json_pointer&)
+    @brief access specified element via JSON Pointer
+
+    Returns a const reference to the element at with specified JSON pointer
+    @a ptr, with bounds checking.
+
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw std::out_of_range      if the JSON pointer can not be resolved
+    @throw std::domain_error      if an array index begins with '0'
+    @throw std::invalid_argument  if an array index was not a number
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer_const}
+
+    @since version 2.0.0
     */
     const_reference at(const json_pointer& ptr) const
     {