From 70fc5835cb4758b73fdab800380a7e3246ff79f7 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Mon, 18 Apr 2016 22:41:36 +0200
Subject: [PATCH 01/19] started implementing JSON Patch (RFC 6902)

---
 src/json.hpp      |  95 +++++++++++++++++++
 src/json.hpp.re2c |  95 +++++++++++++++++++
 test/unit.cpp     | 226 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 416 insertions(+)

diff --git a/src/json.hpp b/src/json.hpp
index 57a8f4c7..421b9995 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9492,6 +9492,101 @@ basic_json_parser_63:
     }
 
     /// @}
+
+    /*!
+    @brief applies a JSON patch
+
+    @param[in] patch  JSON patch document
+    @return patched document
+
+    @note The original JSON value is not changed; that is, the patch is
+          applied to a copy of the value.
+
+    @sa [RFC 6902](https://tools.ietf.org/html/rfc6902)
+    */
+    basic_json apply_patch(const basic_json& patch) const
+    {
+        basic_json result = *this;
+
+        if (not patch.is_array())
+        {
+            // a JSON patch must be an array of objects
+            throw std::domain_error("JSON patch must be an array of objects");
+        }
+
+        for (const auto& val : patch)
+        {
+            if (not val.is_object())
+            {
+                throw std::domain_error("JSON patch must be an array of objects");
+            }
+
+            // collect members
+            const auto it_op = val.m_value.object->find("op");
+            const auto it_path = val.m_value.object->find("path");
+            const auto it_value = val.m_value.object->find("value");
+
+            if (it_op == val.m_value.object->end() or not it_op->second.is_string())
+            {
+                throw std::domain_error("operation must have a string 'op' member");
+            }
+
+            if (it_path == val.m_value.object->end() or not it_op->second.is_string())
+            {
+                throw std::domain_error("operation must have a string 'path' member");
+            }
+
+            const std::string op = it_op->second;
+            const std::string path = it_path->second;
+            const json_pointer ptr(path);
+
+            if (op == "add")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'add' operation must have member 'value'");
+                }
+
+                result[ptr] = it_value->second;
+            }
+            else if (op == "remove")
+            {
+            }
+            else if (op == "replace")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'replace' operation must have member 'value'");
+                }
+            }
+            else if (op == "move")
+            {
+            }
+            else if (op == "copy")
+            {
+            }
+            else if (op == "test")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'test' operation must have member 'value'");
+                }
+
+                if (result.at(ptr) != it_value->second)
+                {
+                    throw std::domain_error("unsuccessful: " + val.dump());
+                }
+            }
+            else
+            {
+                // op must be "add", "remove", "replace", "move",
+                // "copy", or "test"
+                throw std::domain_error("operation value '" + op + "' is invalid");
+            }
+        }
+
+        return result;
+    }
 };
 
 
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index cdd96ee9..3f839737 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8802,6 +8802,101 @@ class basic_json
     }
 
     /// @}
+
+    /*!
+    @brief applies a JSON patch
+
+    @param[in] patch  JSON patch document
+    @return patched document
+
+    @note The original JSON value is not changed; that is, the patch is
+          applied to a copy of the value.
+
+    @sa [RFC 6902](https://tools.ietf.org/html/rfc6902)
+    */
+    basic_json apply_patch(const basic_json& patch) const
+    {
+        basic_json result = *this;
+
+        if (not patch.is_array())
+        {
+            // a JSON patch must be an array of objects
+            throw std::domain_error("JSON patch must be an array of objects");
+        }
+
+        for (const auto& val : patch)
+        {
+            if (not val.is_object())
+            {
+                throw std::domain_error("JSON patch must be an array of objects");
+            }
+
+            // collect members
+            const auto it_op = val.m_value.object->find("op");
+            const auto it_path = val.m_value.object->find("path");
+            const auto it_value = val.m_value.object->find("value");
+
+            if (it_op == val.m_value.object->end() or not it_op->second.is_string())
+            {
+                throw std::domain_error("operation must have a string 'op' member");
+            }
+
+            if (it_path == val.m_value.object->end() or not it_op->second.is_string())
+            {
+                throw std::domain_error("operation must have a string 'path' member");
+            }
+
+            const std::string op = it_op->second;
+            const std::string path = it_path->second;
+            const json_pointer ptr(path);
+
+            if (op == "add")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'add' operation must have member 'value'");
+                }
+
+                result[ptr] = it_value->second;
+            }
+            else if (op == "remove")
+            {
+            }
+            else if (op == "replace")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'replace' operation must have member 'value'");
+                }
+            }
+            else if (op == "move")
+            {
+            }
+            else if (op == "copy")
+            {
+            }
+            else if (op == "test")
+            {
+                if (it_value == val.m_value.object->end())
+                {
+                    throw std::domain_error("'test' operation must have member 'value'");
+                }
+
+                if (result.at(ptr) != it_value->second)
+                {
+                    throw std::domain_error("unsuccessful: " + val.dump());
+                }
+            }
+            else
+            {
+                // op must be "add", "remove", "replace", "move",
+                // "copy", or "test"
+                throw std::domain_error("operation value '" + op + "' is invalid");
+            }
+        }
+
+        return result;
+    }
 };
 
 
diff --git a/test/unit.cpp b/test/unit.cpp
index 2666e111..7a91efd7 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12391,6 +12391,232 @@ TEST_CASE("JSON pointers")
     }
 }
 
+TEST_CASE("JSON patch")
+{
+    SECTION("examples from RFC 6902")
+    {
+        SECTION("example A.1 - Adding an Object Member")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": "bar"}
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/baz", "value": "qux" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                {
+                    "baz": "qux",
+                    "foo": "bar"
+                }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.8 - Testing a Value: Success")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                     "baz": "qux",
+                     "foo": [ "a", 2, "c" ]
+                }
+            )"_json;
+
+            // A JSON Patch document that will result in successful evaluation:
+            json patch = R"(
+                [
+                    { "op": "test", "path": "/baz", "value": "qux" },
+                    { "op": "test", "path": "/foo/1", "value": 2 }
+                ]
+            )"_json;
+
+            // check if evaluation does not throw
+            CHECK_NOTHROW(doc.apply_patch(patch));
+            // check if patched document is unchanged
+            CHECK(doc.apply_patch(patch) == doc);
+        }
+
+        SECTION("example A.9 - Testing a Value: Error")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "baz": "qux" }
+            )"_json;
+
+            // A JSON Patch document that will result in an error condition:
+            json patch = R"(
+                [
+                    { "op": "test", "path": "/baz", "value": "bar" }
+                ]
+            )"_json;
+
+            // check that evaluation throws
+            CHECK_THROWS_AS(doc.apply_patch(patch), std::domain_error);
+            CHECK_THROWS_WITH(doc.apply_patch(patch), "unsuccessful: " + patch[0].dump());
+        }
+
+        SECTION("example A.10 - Adding a Nested Member Object")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": "bar" }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/child", "value": { "grandchild": { } } }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+            {
+                "foo": "bar",
+                "child": {
+                    "grandchild": {
+                    }
+                }
+            }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.11 - Ignoring Unrecognized Elements")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": "bar" }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
+                ]
+            )"_json;
+
+            json expected = R"(
+                {
+                    "foo": "bar",
+                    "baz": "qux"
+                } 
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.12 - Adding to a Nonexistent Target")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": "bar" }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/baz/bat", "value": "qux" }
+                ]
+            )"_json;
+
+            // This JSON Patch document, applied to the target JSON document
+            // above, would result in an error (therefore, it would not be
+            // applied), because the "add" operation's target location that
+            // references neither the root of the document, nor a member of
+            // an existing object, nor a member of an existing array.
+
+            CHECK_THROWS_AS(doc.apply_patch(patch), std::out_of_range);
+            CHECK_THROWS_WITH(doc.apply_patch(patch), "unresolved reference token 'bat'");
+        }
+
+        SECTION("example A.14 - Escape Ordering")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "/": 9,
+                    "~1": 10
+                }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    {"op": "test", "path": "/~01", "value": 10}
+                ]
+            )"_json;
+
+            json expected = R"(
+                {
+                    "/": 9,
+                    "~1": 10
+                } 
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.15 - Comparing Strings and Numbers")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "/": 9,
+                    "~1": 10
+                } 
+            )"_json;
+
+            // A JSON Patch document that will result in an error condition:
+            json patch = R"(
+                [
+                    {"op": "test", "path": "/~01", "value": "10"}
+                ]
+            )"_json;
+
+            // check that evaluation throws
+            CHECK_THROWS_AS(doc.apply_patch(patch), std::domain_error);
+            CHECK_THROWS_WITH(doc.apply_patch(patch), "unsuccessful: " + patch[0].dump());
+        }
+
+        SECTION("example A.16 - Adding an Array Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": ["bar"] }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                { "foo": ["bar", ["abc", "def"]] }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+    }
+}
+
 TEST_CASE("regression tests")
 {
     SECTION("issue #60 - Double quotation mark is not parsed correctly")

From fa03cf0c63544a358c8b92db8fe85317a826174a Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Wed, 20 Apr 2016 15:41:33 +0200
Subject: [PATCH 02/19] replace and copy

---
 src/json.hpp      | 19 +++++++++++++
 src/json.hpp.re2c | 19 +++++++++++++
 test/unit.cpp     | 70 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+)

diff --git a/src/json.hpp b/src/json.hpp
index 421b9995..acde03bf 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9525,6 +9525,7 @@ basic_json_parser_63:
             const auto it_op = val.m_value.object->find("op");
             const auto it_path = val.m_value.object->find("path");
             const auto it_value = val.m_value.object->find("value");
+            const auto it_from = val.m_value.object->find("from");
 
             if (it_op == val.m_value.object->end() or not it_op->second.is_string())
             {
@@ -9558,12 +9559,30 @@ basic_json_parser_63:
                 {
                     throw std::domain_error("'replace' operation must have member 'value'");
                 }
+
+                result.at(ptr) = it_value->second;
             }
             else if (op == "move")
             {
+                if (it_from == val.m_value.object->end())
+                {
+                    throw std::domain_error("'move' operation must have member 'from'");
+                }
+
+                const std::string from_path = it_from->second;
+                const json_pointer from_ptr(from_path);
             }
             else if (op == "copy")
             {
+                if (it_from == val.m_value.object->end())
+                {
+                    throw std::domain_error("'copy' operation must have member 'from'");
+                }
+
+                const std::string from_path = it_from->second;
+                const json_pointer from_ptr(from_path);
+
+                result[ptr] = result.at(from_ptr);
             }
             else if (op == "test")
             {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 3f839737..d161f38d 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8835,6 +8835,7 @@ class basic_json
             const auto it_op = val.m_value.object->find("op");
             const auto it_path = val.m_value.object->find("path");
             const auto it_value = val.m_value.object->find("value");
+            const auto it_from = val.m_value.object->find("from");
 
             if (it_op == val.m_value.object->end() or not it_op->second.is_string())
             {
@@ -8868,12 +8869,30 @@ class basic_json
                 {
                     throw std::domain_error("'replace' operation must have member 'value'");
                 }
+
+                result.at(ptr) = it_value->second;
             }
             else if (op == "move")
             {
+                if (it_from == val.m_value.object->end())
+                {
+                    throw std::domain_error("'move' operation must have member 'from'");
+                }
+
+                const std::string from_path = it_from->second;
+                const json_pointer from_ptr(from_path);
             }
             else if (op == "copy")
             {
+                if (it_from == val.m_value.object->end())
+                {
+                    throw std::domain_error("'copy' operation must have member 'from'");
+                }
+
+                const std::string from_path = it_from->second;
+                const json_pointer from_ptr(from_path);
+
+                result[ptr] = result.at(from_ptr);
             }
             else if (op == "test")
             {
diff --git a/test/unit.cpp b/test/unit.cpp
index 7a91efd7..7b675462 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12421,6 +12421,34 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
 
+        SECTION("example A.5 - Replacing a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "baz": "qux",
+                    "foo": "bar"
+                }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "replace", "path": "/baz", "value": "boo" }
+                ]
+            )"_json;
+
+            json expected = R"(
+                {
+                    "baz": "boo",
+                    "foo": "bar"
+                }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
         SECTION("example A.8 - Testing a Value: Success")
         {
             // An example target JSON document:
@@ -12615,6 +12643,48 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
     }
+
+    SECTION("own examples")
+    {
+        SECTION("copy")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "foo": {
+                        "bar": "baz",
+                        "waldo": "fred"
+                    },
+                    "qux": {
+                       "corge": "grault"
+                    }
+                }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" }
+                ]
+            )"_json;
+
+            json expected = R"(
+                {
+                    "foo": {
+                        "bar": "baz",
+                        "waldo": "fred"
+                    },
+                    "qux": {
+                       "corge": "grault",
+                       "thud": "fred"
+                    }
+                }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+    }
 }
 
 TEST_CASE("regression tests")

From 397ada22d35cf2ebc168a48f264218c069f13d74 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Wed, 20 Apr 2016 16:52:00 +0200
Subject: [PATCH 03/19] implemented remove

---
 src/json.hpp      | 26 +++++++++++++++++++++++--
 src/json.hpp.re2c | 26 +++++++++++++++++++++++--
 test/unit.cpp     | 49 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 4 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index acde03bf..de6c6ec1 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8969,6 +8969,18 @@ basic_json_parser_63:
             : reference_tokens(split(s))
         {}
 
+        std::string pop_back()
+        {
+            if (reference_tokens.empty())
+            {
+                throw std::domain_error("JSON pointer has no parent");
+            }
+
+            auto last = reference_tokens.back();
+            reference_tokens.pop_back();
+            return last;
+        }
+
       private:
         /*!
         @brief create and return a reference to the pointed to value
@@ -9420,7 +9432,7 @@ basic_json_parser_63:
 
       private:
         /// the reference tokens
-        const std::vector<std::string> reference_tokens {};
+        std::vector<std::string> reference_tokens {};
     };
 
     ////////////////////////////
@@ -9539,7 +9551,7 @@ basic_json_parser_63:
 
             const std::string op = it_op->second;
             const std::string path = it_path->second;
-            const json_pointer ptr(path);
+            json_pointer ptr(path);
 
             if (op == "add")
             {
@@ -9552,6 +9564,16 @@ basic_json_parser_63:
             }
             else if (op == "remove")
             {
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result.at(ptr);
+                if (parent.is_object())
+                {
+                    parent.erase(parent.find(last_path));
+                }
+                else if (parent.is_array())
+                {
+                    parent.erase(parent.begin() + std::stoi(last_path));
+                }
             }
             else if (op == "replace")
             {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index d161f38d..073baf36 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8279,6 +8279,18 @@ class basic_json
             : reference_tokens(split(s))
         {}
 
+        std::string pop_back()
+        {
+            if (reference_tokens.empty())
+            {
+                throw std::domain_error("JSON pointer has no parent");
+            }
+
+            auto last = reference_tokens.back();
+            reference_tokens.pop_back();
+            return last;
+        }
+
       private:
         /*!
         @brief create and return a reference to the pointed to value
@@ -8730,7 +8742,7 @@ class basic_json
 
       private:
         /// the reference tokens
-        const std::vector<std::string> reference_tokens {};
+        std::vector<std::string> reference_tokens {};
     };
 
     ////////////////////////////
@@ -8849,7 +8861,7 @@ class basic_json
 
             const std::string op = it_op->second;
             const std::string path = it_path->second;
-            const json_pointer ptr(path);
+            json_pointer ptr(path);
 
             if (op == "add")
             {
@@ -8862,6 +8874,16 @@ class basic_json
             }
             else if (op == "remove")
             {
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result.at(ptr);
+                if (parent.is_object())
+                {
+                    parent.erase(parent.find(last_path));
+                }
+                else if (parent.is_array())
+                {
+                    parent.erase(parent.begin() + std::stoi(last_path));
+                }
             }
             else if (op == "replace")
             {
diff --git a/test/unit.cpp b/test/unit.cpp
index 7b675462..6f9ad8f5 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12421,6 +12421,55 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
 
+        SECTION("example A.3 - Removing an Object Member")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "baz": "qux",
+                    "foo": "bar"
+                }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "remove", "path": "/baz" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                { "foo": "bar" }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.4 - Removing an Array Element")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": [ "bar", "qux", "baz" ] }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "remove", "path": "/foo/1" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                { "foo": [ "bar", "baz" ] }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
         SECTION("example A.5 - Replacing a Value")
         {
             // An example target JSON document:

From 855cf2307bb7c4ac874cb5eaac74f1c4abf01428 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 24 Apr 2016 16:51:06 +0200
Subject: [PATCH 04/19] extended "add" to cope with arrays

---
 src/json.hpp      | 20 +++++++++++++++++++-
 src/json.hpp.re2c | 20 +++++++++++++++++++-
 test/unit.cpp     | 28 +++++++++++++++++++++++++++-
 3 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index de6c6ec1..c1bf6648 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9560,7 +9560,25 @@ basic_json_parser_63:
                     throw std::domain_error("'add' operation must have member 'value'");
                 }
 
-                result[ptr] = it_value->second;
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result.at(ptr);
+
+                if (parent.is_object())
+                {
+                    parent[last_path] = it_value->second;
+                }
+                else if (parent.is_array())
+                {
+                    if (last_path == "-")
+                    {
+                        parent.push_back(it_value->second);
+                    }
+                    else
+                    {
+                        parent.insert(parent.begin() + std::stoi(last_path),
+                                      it_value->second);
+                    }
+                }
             }
             else if (op == "remove")
             {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 073baf36..aa02bbea 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8870,7 +8870,25 @@ class basic_json
                     throw std::domain_error("'add' operation must have member 'value'");
                 }
 
-                result[ptr] = it_value->second;
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result.at(ptr);
+
+                if (parent.is_object())
+                {
+                    parent[last_path] = it_value->second;
+                }
+                else if (parent.is_array())
+                {
+                    if (last_path == "-")
+                    {
+                        parent.push_back(it_value->second);
+                    }
+                    else
+                    {
+                        parent.insert(parent.begin() + std::stoi(last_path),
+                                      it_value->second);
+                    }
+                }
             }
             else if (op == "remove")
             {
diff --git a/test/unit.cpp b/test/unit.cpp
index 6f9ad8f5..277daed1 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12421,6 +12421,29 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
 
+        SECTION("example A.2 - Adding an Array Element")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": [ "bar", "baz" ] }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "add", "path": "/foo/1", "value": "qux" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                { "foo": [ "bar", "qux", "baz" ] }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
         SECTION("example A.3 - Removing an Object Member")
         {
             // An example target JSON document:
@@ -12616,9 +12639,12 @@ TEST_CASE("JSON patch")
             // an existing object, nor a member of an existing array.
 
             CHECK_THROWS_AS(doc.apply_patch(patch), std::out_of_range);
-            CHECK_THROWS_WITH(doc.apply_patch(patch), "unresolved reference token 'bat'");
+            CHECK_THROWS_WITH(doc.apply_patch(patch), "key 'baz' not found");
         }
 
+        // A.13.  Invalid JSON Patch Document
+        // not applicable
+
         SECTION("example A.14 - Escape Ordering")
         {
             // An example target JSON document:

From 09e9f6dcd4b61005e0b37f364cdadeda4d6ea9a1 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 24 Apr 2016 17:43:27 +0200
Subject: [PATCH 05/19] implemented "move"

---
 src/json.hpp      | 75 ++++++++++++++++++++++++++++-------------------
 src/json.hpp.re2c | 75 ++++++++++++++++++++++++++++-------------------
 test/unit.cpp     | 62 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 152 insertions(+), 60 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index c1bf6648..8d5291ce 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9526,6 +9526,44 @@ basic_json_parser_63:
             throw std::domain_error("JSON patch must be an array of objects");
         }
 
+        const auto operation_add = [&result](json_pointer & ptr,
+                                             basic_json & value)
+        {
+            const auto last_path = ptr.pop_back();
+            basic_json& parent = result.at(ptr);
+
+            if (parent.is_object())
+            {
+                parent[last_path] = value;
+            }
+            else if (parent.is_array())
+            {
+                if (last_path == "-")
+                {
+                    parent.push_back(value);
+                }
+                else
+                {
+                    parent.insert(parent.begin() + std::stoi(last_path),
+                                  value);
+                }
+            }
+        };
+
+        const auto operation_remove = [&result](json_pointer & ptr)
+        {
+            const auto last_path = ptr.pop_back();
+            basic_json& parent = result.at(ptr);
+            if (parent.is_object())
+            {
+                parent.erase(parent.find(last_path));
+            }
+            else if (parent.is_array())
+            {
+                parent.erase(parent.begin() + std::stoi(last_path));
+            }
+        };
+
         for (const auto& val : patch)
         {
             if (not val.is_object())
@@ -9560,38 +9598,11 @@ basic_json_parser_63:
                     throw std::domain_error("'add' operation must have member 'value'");
                 }
 
-                const auto last_path = ptr.pop_back();
-                basic_json& parent = result.at(ptr);
-
-                if (parent.is_object())
-                {
-                    parent[last_path] = it_value->second;
-                }
-                else if (parent.is_array())
-                {
-                    if (last_path == "-")
-                    {
-                        parent.push_back(it_value->second);
-                    }
-                    else
-                    {
-                        parent.insert(parent.begin() + std::stoi(last_path),
-                                      it_value->second);
-                    }
-                }
+                operation_add(ptr, it_value->second);
             }
             else if (op == "remove")
             {
-                const auto last_path = ptr.pop_back();
-                basic_json& parent = result.at(ptr);
-                if (parent.is_object())
-                {
-                    parent.erase(parent.find(last_path));
-                }
-                else if (parent.is_array())
-                {
-                    parent.erase(parent.begin() + std::stoi(last_path));
-                }
+                operation_remove(ptr);
             }
             else if (op == "replace")
             {
@@ -9610,7 +9621,11 @@ basic_json_parser_63:
                 }
 
                 const std::string from_path = it_from->second;
-                const json_pointer from_ptr(from_path);
+                json_pointer from_ptr(from_path);
+                basic_json v = result[from_ptr];
+
+                operation_remove(from_ptr);
+                operation_add(ptr, v);
             }
             else if (op == "copy")
             {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index aa02bbea..37feeec6 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8836,6 +8836,44 @@ class basic_json
             throw std::domain_error("JSON patch must be an array of objects");
         }
 
+        const auto operation_add = [&result](json_pointer & ptr,
+                                             basic_json & value)
+        {
+            const auto last_path = ptr.pop_back();
+            basic_json& parent = result.at(ptr);
+
+            if (parent.is_object())
+            {
+                parent[last_path] = value;
+            }
+            else if (parent.is_array())
+            {
+                if (last_path == "-")
+                {
+                    parent.push_back(value);
+                }
+                else
+                {
+                    parent.insert(parent.begin() + std::stoi(last_path),
+                                  value);
+                }
+            }
+        };
+
+        const auto operation_remove = [&result](json_pointer & ptr)
+        {
+            const auto last_path = ptr.pop_back();
+            basic_json& parent = result.at(ptr);
+            if (parent.is_object())
+            {
+                parent.erase(parent.find(last_path));
+            }
+            else if (parent.is_array())
+            {
+                parent.erase(parent.begin() + std::stoi(last_path));
+            }
+        };
+
         for (const auto& val : patch)
         {
             if (not val.is_object())
@@ -8870,38 +8908,11 @@ class basic_json
                     throw std::domain_error("'add' operation must have member 'value'");
                 }
 
-                const auto last_path = ptr.pop_back();
-                basic_json& parent = result.at(ptr);
-
-                if (parent.is_object())
-                {
-                    parent[last_path] = it_value->second;
-                }
-                else if (parent.is_array())
-                {
-                    if (last_path == "-")
-                    {
-                        parent.push_back(it_value->second);
-                    }
-                    else
-                    {
-                        parent.insert(parent.begin() + std::stoi(last_path),
-                                      it_value->second);
-                    }
-                }
+                operation_add(ptr, it_value->second);
             }
             else if (op == "remove")
             {
-                const auto last_path = ptr.pop_back();
-                basic_json& parent = result.at(ptr);
-                if (parent.is_object())
-                {
-                    parent.erase(parent.find(last_path));
-                }
-                else if (parent.is_array())
-                {
-                    parent.erase(parent.begin() + std::stoi(last_path));
-                }
+                operation_remove(ptr);
             }
             else if (op == "replace")
             {
@@ -8920,7 +8931,11 @@ class basic_json
                 }
 
                 const std::string from_path = it_from->second;
-                const json_pointer from_ptr(from_path);
+                json_pointer from_ptr(from_path);
+                basic_json v = result[from_ptr];
+
+                operation_remove(from_ptr);
+                operation_add(ptr, v);
             }
             else if (op == "copy")
             {
diff --git a/test/unit.cpp b/test/unit.cpp
index 277daed1..907e68bf 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12521,6 +12521,68 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
 
+        SECTION("example A.6 - Moving a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                {
+                    "foo": {
+                       "bar": "baz",
+                        "waldo": "fred"
+                    },
+                    "qux": {
+                        "corge": "grault"
+                    }
+                }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                {
+                    "foo": {
+                       "bar": "baz"
+                    },
+                    "qux": {
+                        "corge": "grault",
+                        "thud": "fred"
+                    }
+                }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
+        SECTION("example A.7 - Moving a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                { "foo": [ "all", "grass", "cows", "eat" ] }
+            )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                [
+                    { "op": "move", "from": "/foo/1", "path": "/foo/3" }
+                ]
+            )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                { "foo": [ "all", "cows", "eat", "grass" ] }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.apply_patch(patch) == expected);
+        }
+
         SECTION("example A.8 - Testing a Value: Success")
         {
             // An example target JSON document:

From fb54e212b666ecc37272740278384a8bc9fc2f5f Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 24 Apr 2016 19:03:33 +0200
Subject: [PATCH 06/19] clean up and added tests

---
 src/json.hpp      | 120 ++++++++++++++---------------
 src/json.hpp.re2c | 120 ++++++++++++++---------------
 test/unit.cpp     | 192 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 310 insertions(+), 122 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 8d5291ce..b7a6f64a 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9518,42 +9518,44 @@ basic_json_parser_63:
     */
     basic_json apply_patch(const basic_json& patch) const
     {
+        // make a working copy to apply the patch to
         basic_json result = *this;
 
-        if (not patch.is_array())
-        {
-            // a JSON patch must be an array of objects
-            throw std::domain_error("JSON patch must be an array of objects");
-        }
-
-        const auto operation_add = [&result](json_pointer & ptr,
-                                             basic_json & value)
+        // wrapper for "add" operation; add value at ptr
+        const auto operation_add = [&result](json_pointer & ptr, basic_json value)
         {
+            // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
             basic_json& parent = result.at(ptr);
 
             if (parent.is_object())
             {
+                // use operator[] to add value
                 parent[last_path] = value;
             }
             else if (parent.is_array())
             {
                 if (last_path == "-")
                 {
+                    // special case: append to back
                     parent.push_back(value);
                 }
                 else
                 {
-                    parent.insert(parent.begin() + std::stoi(last_path),
-                                  value);
+                    // default case: insert add offset
+                    parent.insert(parent.begin() + std::stoi(last_path), value);
                 }
             }
         };
 
+        // wrapper for "remove" operation; remove value at ptr
         const auto operation_remove = [&result](json_pointer & ptr)
         {
+            // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
             basic_json& parent = result.at(ptr);
+
+            // remove child
             if (parent.is_object())
             {
                 parent.erase(parent.find(last_path));
@@ -9564,41 +9566,57 @@ basic_json_parser_63:
             }
         };
 
+        // type check
+        if (not patch.is_array())
+        {
+            // a JSON patch must be an array of objects
+            throw std::domain_error("JSON patch must be an array of objects");
+        }
+
+        // iterate and apply th eoperations
         for (const auto& val : patch)
         {
+            // wrapper to get a value for an operation
+            const auto get_value = [&val](const std::string & op,
+                                          const std::string & member,
+                                          bool string_type = false) -> basic_json&
+            {
+                // find value
+                auto it = val.m_value.object->find(member);
+
+                // context-sensitive error message
+                const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
+
+                // check if desired value is present
+                if (it == val.m_value.object->end())
+                {
+                    throw std::domain_error(error_msg + " must have member '" + member + "'");
+                }
+
+                // check if result is of type string
+                if (string_type and not it->second.is_string())
+                {
+                    throw std::domain_error(error_msg + " must have string member '" + member + "'");
+                }
+
+                // no error: return value
+                return it->second;
+            };
+
+            // type check
             if (not val.is_object())
             {
                 throw std::domain_error("JSON patch must be an array of objects");
             }
 
-            // collect members
-            const auto it_op = val.m_value.object->find("op");
-            const auto it_path = val.m_value.object->find("path");
-            const auto it_value = val.m_value.object->find("value");
-            const auto it_from = val.m_value.object->find("from");
-
-            if (it_op == val.m_value.object->end() or not it_op->second.is_string())
-            {
-                throw std::domain_error("operation must have a string 'op' member");
-            }
-
-            if (it_path == val.m_value.object->end() or not it_op->second.is_string())
-            {
-                throw std::domain_error("operation must have a string 'path' member");
-            }
-
-            const std::string op = it_op->second;
-            const std::string path = it_path->second;
-            json_pointer ptr(path);
+            // collect mandatory members
+            const std::string op = get_value("op", "op", true);
+            const std::string path = get_value(op, "path", true);
+            json_pointer ptr(get_value(op, "path", true));
 
             if (op == "add")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'add' operation must have member 'value'");
-                }
-
-                operation_add(ptr, it_value->second);
+                operation_add(ptr, get_value("add", "value"));
             }
             else if (op == "remove")
             {
@@ -9606,21 +9624,11 @@ basic_json_parser_63:
             }
             else if (op == "replace")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'replace' operation must have member 'value'");
-                }
-
-                result.at(ptr) = it_value->second;
+                result.at(ptr) = get_value("replace", "value");
             }
             else if (op == "move")
             {
-                if (it_from == val.m_value.object->end())
-                {
-                    throw std::domain_error("'move' operation must have member 'from'");
-                }
-
-                const std::string from_path = it_from->second;
+                const std::string from_path = get_value("move", "from", true);
                 json_pointer from_ptr(from_path);
                 basic_json v = result[from_ptr];
 
@@ -9629,32 +9637,22 @@ basic_json_parser_63:
             }
             else if (op == "copy")
             {
-                if (it_from == val.m_value.object->end())
-                {
-                    throw std::domain_error("'copy' operation must have member 'from'");
-                }
-
-                const std::string from_path = it_from->second;
+                const std::string from_path = get_value("copy", "from", true);;
                 const json_pointer from_ptr(from_path);
 
                 result[ptr] = result.at(from_ptr);
             }
             else if (op == "test")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'test' operation must have member 'value'");
-                }
-
-                if (result.at(ptr) != it_value->second)
+                if (result.at(ptr) != get_value("test", "value"))
                 {
                     throw std::domain_error("unsuccessful: " + val.dump());
                 }
             }
             else
             {
-                // op must be "add", "remove", "replace", "move",
-                // "copy", or "test"
+                // op must be "add", "remove", "replace", "move", "copy", or
+                // "test"
                 throw std::domain_error("operation value '" + op + "' is invalid");
             }
         }
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 37feeec6..c4c87f5d 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8828,42 +8828,44 @@ class basic_json
     */
     basic_json apply_patch(const basic_json& patch) const
     {
+        // make a working copy to apply the patch to
         basic_json result = *this;
 
-        if (not patch.is_array())
-        {
-            // a JSON patch must be an array of objects
-            throw std::domain_error("JSON patch must be an array of objects");
-        }
-
-        const auto operation_add = [&result](json_pointer & ptr,
-                                             basic_json & value)
+        // wrapper for "add" operation; add value at ptr
+        const auto operation_add = [&result](json_pointer & ptr, basic_json value)
         {
+            // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
             basic_json& parent = result.at(ptr);
 
             if (parent.is_object())
             {
+                // use operator[] to add value
                 parent[last_path] = value;
             }
             else if (parent.is_array())
             {
                 if (last_path == "-")
                 {
+                    // special case: append to back
                     parent.push_back(value);
                 }
                 else
                 {
-                    parent.insert(parent.begin() + std::stoi(last_path),
-                                  value);
+                    // default case: insert add offset
+                    parent.insert(parent.begin() + std::stoi(last_path), value);
                 }
             }
         };
 
+        // wrapper for "remove" operation; remove value at ptr
         const auto operation_remove = [&result](json_pointer & ptr)
         {
+            // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
             basic_json& parent = result.at(ptr);
+
+            // remove child
             if (parent.is_object())
             {
                 parent.erase(parent.find(last_path));
@@ -8874,41 +8876,57 @@ class basic_json
             }
         };
 
+        // type check
+        if (not patch.is_array())
+        {
+            // a JSON patch must be an array of objects
+            throw std::domain_error("JSON patch must be an array of objects");
+        }
+
+        // iterate and apply th eoperations
         for (const auto& val : patch)
         {
+            // wrapper to get a value for an operation
+            const auto get_value = [&val](const std::string & op,
+                                          const std::string & member,
+                                          bool string_type = false) -> basic_json&
+            {
+                // find value
+                auto it = val.m_value.object->find(member);
+
+                // context-sensitive error message
+                const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
+
+                // check if desired value is present
+                if (it == val.m_value.object->end())
+                {
+                    throw std::domain_error(error_msg + " must have member '" + member + "'");
+                }
+
+                // check if result is of type string
+                if (string_type and not it->second.is_string())
+                {
+                    throw std::domain_error(error_msg + " must have string member '" + member + "'");
+                }
+
+                // no error: return value
+                return it->second;
+            };
+
+            // type check
             if (not val.is_object())
             {
                 throw std::domain_error("JSON patch must be an array of objects");
             }
 
-            // collect members
-            const auto it_op = val.m_value.object->find("op");
-            const auto it_path = val.m_value.object->find("path");
-            const auto it_value = val.m_value.object->find("value");
-            const auto it_from = val.m_value.object->find("from");
-
-            if (it_op == val.m_value.object->end() or not it_op->second.is_string())
-            {
-                throw std::domain_error("operation must have a string 'op' member");
-            }
-
-            if (it_path == val.m_value.object->end() or not it_op->second.is_string())
-            {
-                throw std::domain_error("operation must have a string 'path' member");
-            }
-
-            const std::string op = it_op->second;
-            const std::string path = it_path->second;
-            json_pointer ptr(path);
+            // collect mandatory members
+            const std::string op = get_value("op", "op", true);
+            const std::string path = get_value(op, "path", true);
+            json_pointer ptr(get_value(op, "path", true));
 
             if (op == "add")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'add' operation must have member 'value'");
-                }
-
-                operation_add(ptr, it_value->second);
+                operation_add(ptr, get_value("add", "value"));
             }
             else if (op == "remove")
             {
@@ -8916,21 +8934,11 @@ class basic_json
             }
             else if (op == "replace")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'replace' operation must have member 'value'");
-                }
-
-                result.at(ptr) = it_value->second;
+                result.at(ptr) = get_value("replace", "value");
             }
             else if (op == "move")
             {
-                if (it_from == val.m_value.object->end())
-                {
-                    throw std::domain_error("'move' operation must have member 'from'");
-                }
-
-                const std::string from_path = it_from->second;
+                const std::string from_path = get_value("move", "from", true);
                 json_pointer from_ptr(from_path);
                 basic_json v = result[from_ptr];
 
@@ -8939,32 +8947,22 @@ class basic_json
             }
             else if (op == "copy")
             {
-                if (it_from == val.m_value.object->end())
-                {
-                    throw std::domain_error("'copy' operation must have member 'from'");
-                }
-
-                const std::string from_path = it_from->second;
+                const std::string from_path = get_value("copy", "from", true);;
                 const json_pointer from_ptr(from_path);
 
                 result[ptr] = result.at(from_ptr);
             }
             else if (op == "test")
             {
-                if (it_value == val.m_value.object->end())
-                {
-                    throw std::domain_error("'test' operation must have member 'value'");
-                }
-
-                if (result.at(ptr) != it_value->second)
+                if (result.at(ptr) != get_value("test", "value"))
                 {
                     throw std::domain_error("unsuccessful: " + val.dump());
                 }
             }
             else
             {
-                // op must be "add", "remove", "replace", "move",
-                // "copy", or "test"
+                // op must be "add", "remove", "replace", "move", "copy", or
+                // "test"
                 throw std::domain_error("operation value '" + op + "' is invalid");
             }
         }
diff --git a/test/unit.cpp b/test/unit.cpp
index 907e68bf..664648cb 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12822,6 +12822,198 @@ TEST_CASE("JSON patch")
             CHECK(doc.apply_patch(patch) == expected);
         }
     }
+
+    SECTION("errors")
+    {
+        SECTION("unknown operation")
+        {
+            SECTION("missing 'op'")
+            {
+                json j;
+                json patch = {{{"foo", "bar"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have member 'op'");
+            }
+
+            SECTION("non-string 'op'")
+            {
+                json j;
+                json patch = {{{"op", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have string member 'op'");
+            }
+        }
+
+        SECTION("add")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "add"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "add"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have string member 'path'");
+            }
+
+            SECTION("missing 'value'")
+            {
+                json j;
+                json patch = {{{"op", "add"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'value'");
+            }
+        }
+
+        SECTION("remove")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "remove"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "remove"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have string member 'path'");
+            }
+        }
+
+        SECTION("replace")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "replace"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "replace"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have string member 'path'");
+            }
+
+            SECTION("missing 'value'")
+            {
+                json j;
+                json patch = {{{"op", "replace"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'value'");
+            }
+        }
+
+        SECTION("move")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "move"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "move"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'path'");
+            }
+
+            SECTION("missing 'from'")
+            {
+                json j;
+                json patch = {{{"op", "move"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'from'");
+            }
+
+            SECTION("non-string 'from'")
+            {
+                json j;
+                json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'from'");
+            }
+        }
+
+        SECTION("copy")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "copy"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "copy"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'path'");
+            }
+
+            SECTION("missing 'from'")
+            {
+                json j;
+                json patch = {{{"op", "copy"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'from'");
+            }
+
+            SECTION("non-string 'from'")
+            {
+                json j;
+                json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'from'");
+            }
+        }
+
+        SECTION("test")
+        {
+            SECTION("missing 'path'")
+            {
+                json j;
+                json patch = {{{"op", "test"}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'path'");
+            }
+
+            SECTION("non-string 'path'")
+            {
+                json j;
+                json patch = {{{"op", "test"}, {"path", 1}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have string member 'path'");
+            }
+
+            SECTION("missing 'value'")
+            {
+                json j;
+                json patch = {{{"op", "test"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'value'");
+            }
+        }
+    }
 }
 
 TEST_CASE("regression tests")

From 8d4cf5ef8d0f3ad5bd7efb32dc7d3b44acc386e9 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 24 Apr 2016 19:04:10 +0200
Subject: [PATCH 07/19] oops

---
 src/json.hpp      | 2 +-
 src/json.hpp.re2c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index b7a6f64a..0a1db1c3 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9612,7 +9612,7 @@ basic_json_parser_63:
             // collect mandatory members
             const std::string op = get_value("op", "op", true);
             const std::string path = get_value(op, "path", true);
-            json_pointer ptr(get_value(op, "path", true));
+            json_pointer ptr(path);
 
             if (op == "add")
             {
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index c4c87f5d..52d76148 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8922,7 +8922,7 @@ class basic_json
             // collect mandatory members
             const std::string op = get_value("op", "op", true);
             const std::string path = get_value(op, "path", true);
-            json_pointer ptr(get_value(op, "path", true));
+            json_pointer ptr(path);
 
             if (op == "add")
             {

From 96cfe7463fa4faf32a2e618065955fc4ef43ff57 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 24 Apr 2016 19:09:12 +0200
Subject: [PATCH 08/19] fixed some warnings

---
 src/json.hpp      | 16 ++++++++--------
 src/json.hpp.re2c | 16 ++++++++--------
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/src/json.hpp b/src/json.hpp
index 0a1db1c3..bfe45ada 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9522,7 +9522,7 @@ basic_json_parser_63:
         basic_json result = *this;
 
         // wrapper for "add" operation; add value at ptr
-        const auto operation_add = [&result](json_pointer & ptr, basic_json value)
+        const auto operation_add = [&result](json_pointer & ptr, basic_json val)
         {
             // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
@@ -9531,19 +9531,19 @@ basic_json_parser_63:
             if (parent.is_object())
             {
                 // use operator[] to add value
-                parent[last_path] = value;
+                parent[last_path] = val;
             }
             else if (parent.is_array())
             {
                 if (last_path == "-")
                 {
                     // special case: append to back
-                    parent.push_back(value);
+                    parent.push_back(val);
                 }
                 else
                 {
                     // default case: insert add offset
-                    parent.insert(parent.begin() + std::stoi(last_path), value);
+                    parent.insert(parent.begin() + std::stoi(last_path), val);
                 }
             }
         };
@@ -9579,7 +9579,7 @@ basic_json_parser_63:
             // wrapper to get a value for an operation
             const auto get_value = [&val](const std::string & op,
                                           const std::string & member,
-                                          bool string_type = false) -> basic_json&
+                                          bool string_type) -> basic_json&
             {
                 // find value
                 auto it = val.m_value.object->find(member);
@@ -9616,7 +9616,7 @@ basic_json_parser_63:
 
             if (op == "add")
             {
-                operation_add(ptr, get_value("add", "value"));
+                operation_add(ptr, get_value("add", "value", false));
             }
             else if (op == "remove")
             {
@@ -9624,7 +9624,7 @@ basic_json_parser_63:
             }
             else if (op == "replace")
             {
-                result.at(ptr) = get_value("replace", "value");
+                result.at(ptr) = get_value("replace", "value", false);
             }
             else if (op == "move")
             {
@@ -9644,7 +9644,7 @@ basic_json_parser_63:
             }
             else if (op == "test")
             {
-                if (result.at(ptr) != get_value("test", "value"))
+                if (result.at(ptr) != get_value("test", "value", false))
                 {
                     throw std::domain_error("unsuccessful: " + val.dump());
                 }
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 52d76148..af6df6ed 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8832,7 +8832,7 @@ class basic_json
         basic_json result = *this;
 
         // wrapper for "add" operation; add value at ptr
-        const auto operation_add = [&result](json_pointer & ptr, basic_json value)
+        const auto operation_add = [&result](json_pointer & ptr, basic_json val)
         {
             // get reference to parent of JSON pointer ptr
             const auto last_path = ptr.pop_back();
@@ -8841,19 +8841,19 @@ class basic_json
             if (parent.is_object())
             {
                 // use operator[] to add value
-                parent[last_path] = value;
+                parent[last_path] = val;
             }
             else if (parent.is_array())
             {
                 if (last_path == "-")
                 {
                     // special case: append to back
-                    parent.push_back(value);
+                    parent.push_back(val);
                 }
                 else
                 {
                     // default case: insert add offset
-                    parent.insert(parent.begin() + std::stoi(last_path), value);
+                    parent.insert(parent.begin() + std::stoi(last_path), val);
                 }
             }
         };
@@ -8889,7 +8889,7 @@ class basic_json
             // wrapper to get a value for an operation
             const auto get_value = [&val](const std::string & op,
                                           const std::string & member,
-                                          bool string_type = false) -> basic_json&
+                                          bool string_type) -> basic_json&
             {
                 // find value
                 auto it = val.m_value.object->find(member);
@@ -8926,7 +8926,7 @@ class basic_json
 
             if (op == "add")
             {
-                operation_add(ptr, get_value("add", "value"));
+                operation_add(ptr, get_value("add", "value", false));
             }
             else if (op == "remove")
             {
@@ -8934,7 +8934,7 @@ class basic_json
             }
             else if (op == "replace")
             {
-                result.at(ptr) = get_value("replace", "value");
+                result.at(ptr) = get_value("replace", "value", false);
             }
             else if (op == "move")
             {
@@ -8954,7 +8954,7 @@ class basic_json
             }
             else if (op == "test")
             {
-                if (result.at(ptr) != get_value("test", "value"))
+                if (result.at(ptr) != get_value("test", "value", false))
                 {
                     throw std::domain_error("unsuccessful: " + val.dump());
                 }

From 5e0bf75d6056eee48976f9c4e86ed52461d289aa Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Mon, 25 Apr 2016 23:17:04 +0200
Subject: [PATCH 09/19] cleanup, test, and diff

---
 README.md                 |    2 +-
 doc/examples/diff.cpp     |   34 ++
 doc/examples/diff.link    |    1 +
 doc/examples/diff.output  |   25 +
 doc/examples/patch.cpp    |   30 +
 doc/examples/patch.link   |    1 +
 doc/examples/patch.output |   11 +
 src/json.hpp              |  753 +++++++++++++++++-------
 src/json.hpp.re2c         |  753 +++++++++++++++++-------
 test/unit.cpp             | 1174 +++++++++++++++++++++++++++----------
 10 files changed, 2068 insertions(+), 716 deletions(-)
 create mode 100644 doc/examples/diff.cpp
 create mode 100644 doc/examples/diff.link
 create mode 100644 doc/examples/diff.output
 create mode 100644 doc/examples/patch.cpp
 create mode 100644 doc/examples/patch.link
 create mode 100644 doc/examples/patch.output

diff --git a/README.md b/README.md
index 2dd60bf1..8f4779f9 100644
--- a/README.md
+++ b/README.md
@@ -428,7 +428,7 @@ $ make
 $ ./json_unit "*"
 
 ===============================================================================
-All tests passed (3344416 assertions in 30 test cases)
+All tests passed (3344554 assertions in 31 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/diff.cpp b/doc/examples/diff.cpp
new file mode 100644
index 00000000..d81a58db
--- /dev/null
+++ b/doc/examples/diff.cpp
@@ -0,0 +1,34 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // the source document
+    json source = R"(
+        {
+            "baz": "qux",
+            "foo": "bar"
+        }
+    )"_json;
+
+    // the target document
+    json target = R"(
+        {
+            "baz": "boo",
+            "hello": [
+                "world"
+            ]
+        }
+    )"_json;
+
+    // create the patch
+    json patch = json::diff(source, target);
+
+    // roundtrip
+    json patched_source = source.patch(patch);
+
+    // output patch and roundtrip result
+    std::cout << std::setw(4) << patch << "\n\n"
+              << std::setw(4) << patched_source << std::endl;
+}
diff --git a/doc/examples/diff.link b/doc/examples/diff.link
new file mode 100644
index 00000000..c3e3fa4d
--- /dev/null
+++ b/doc/examples/diff.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/hicmeOK39tBxaluM"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/diff.output b/doc/examples/diff.output
new file mode 100644
index 00000000..7dc79791
--- /dev/null
+++ b/doc/examples/diff.output
@@ -0,0 +1,25 @@
+[
+    {
+        "op": "replace",
+        "path": "/baz",
+        "value": "boo"
+    },
+    {
+        "op": "remove",
+        "path": "/foo"
+    },
+    {
+        "op": "add",
+        "path": "/hello",
+        "value": [
+            "world"
+        ]
+    }
+]
+
+{
+    "baz": "boo",
+    "hello": [
+        "world"
+    ]
+}
diff --git a/doc/examples/patch.cpp b/doc/examples/patch.cpp
new file mode 100644
index 00000000..24a52d59
--- /dev/null
+++ b/doc/examples/patch.cpp
@@ -0,0 +1,30 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // the original document
+    json doc = R"(
+        {
+          "baz": "qux",
+          "foo": "bar"
+        }
+    )"_json;
+
+    // the patch
+    json patch = R"(
+        [
+          { "op": "replace", "path": "/baz", "value": "boo" },
+          { "op": "add", "path": "/hello", "value": ["world"] },
+          { "op": "remove", "path": "/foo"}
+        ]
+    )"_json;
+
+    // apply the patch
+    json patched_doc = doc.patch(patch);
+
+    // output original and patched document
+    std::cout << std::setw(4) << doc << "\n\n"
+              << std::setw(4) << patched_doc << std::endl;
+}
diff --git a/doc/examples/patch.link b/doc/examples/patch.link
new file mode 100644
index 00000000..5d5032b7
--- /dev/null
+++ b/doc/examples/patch.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/lbczW3AzcUbH1Nbo"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/patch.output b/doc/examples/patch.output
new file mode 100644
index 00000000..eb558fe2
--- /dev/null
+++ b/doc/examples/patch.output
@@ -0,0 +1,11 @@
+{
+    "baz": "qux",
+    "foo": "bar"
+}
+
+{
+    "baz": "boo",
+    "hello": [
+        "world"
+    ]
+}
diff --git a/src/json.hpp b/src/json.hpp
index bfe45ada..1785f182 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -3595,121 +3595,6 @@ class basic_json
         }
     }
 
-    /*!
-    @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. Similar to
-    @ref operator[](const typename object_t::key_type&), `null` values
-    are created in arrays and objects if necessary.
-
-    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 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.,operatorjson_pointer}
-
-    @since version 2.0.0
-    */
-    reference operator[](const json_pointer& ptr)
-    {
-        return ptr.get_unchecked(this);
-    }
-
-    /*!
-    @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  JSON pointer to the desired element
-
-    @return const 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.,operatorjson_pointer_const}
-
-    @since version 2.0.0
-    */
-    const_reference operator[](const json_pointer& ptr) const
-    {
-        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,
-    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}
-
-    @since version 2.0.0
-    */
-    reference at(const json_pointer& ptr)
-    {
-        return ptr.get_checked(this);
-    }
-
-    /*!
-    @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
-    {
-        return ptr.get_checked(this);
-    }
-
     /*!
     @brief access specified object element with default value
 
@@ -4145,8 +4030,8 @@ class basic_json
 
     @throw std::domain_error when called on a type other than JSON array;
     example: `"cannot use erase() with null"`
-    @throw std::out_of_range when `idx >= size()`; example: `"index out of
-    range"`
+    @throw std::out_of_range when `idx >= size()`; example: `"array index 17
+    is out of range"`
 
     @complexity Linear in distance between @a idx and the end of the container.
 
@@ -4167,7 +4052,7 @@ class basic_json
         {
             if (idx >= size())
             {
-                throw std::out_of_range("index out of range");
+                throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
             }
 
             assert(m_value.array != nullptr);
@@ -8969,9 +8854,17 @@ basic_json_parser_63:
             : reference_tokens(split(s))
         {}
 
+        /// test for inequality
+        bool operator!=(const json_pointer& rhs) const
+        {
+            return reference_tokens != rhs.reference_tokens;
+        }
+
+      private:
+        /// remove and return last reference pointer
         std::string pop_back()
         {
-            if (reference_tokens.empty())
+            if (is_root())
             {
                 throw std::domain_error("JSON pointer has no parent");
             }
@@ -8981,7 +8874,24 @@ basic_json_parser_63:
             return last;
         }
 
-      private:
+        /// return whether pointer points to the root document
+        bool is_root() const
+        {
+            return reference_tokens.empty();
+        }
+
+        json_pointer top() const
+        {
+            if (is_root())
+            {
+                throw std::domain_error("JSON pointer has no parent");
+            }
+
+            json_pointer result = *this;
+            result.reference_tokens = {reference_tokens[0]};
+            return result;
+        }
+
         /*!
         @brief create and return a reference to the pointed to value
         */
@@ -9020,7 +8930,7 @@ basic_json_parser_63:
                     case value_t::array:
                     {
                         // create an entry in the array
-                        result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -9083,7 +8993,7 @@ basic_json_parser_63:
                         else
                         {
                             // convert array index to number; unchecked access
-                            ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                            ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         }
                         break;
                     }
@@ -9128,7 +9038,7 @@ basic_json_parser_63:
                         }
 
                         // note: at performs range check
-                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -9180,7 +9090,7 @@ basic_json_parser_63:
                         }
 
                         // use unchecked array access
-                        ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -9224,7 +9134,7 @@ basic_json_parser_63:
                         }
 
                         // note: at performs range check
-                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -9291,12 +9201,8 @@ basic_json_parser_63:
                     }
                 }
 
-                // 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", "~");
-
                 // finally, store the reference token
+                unescape(reference_token);
                 result.push_back(reference_token);
             }
 
@@ -9332,6 +9238,24 @@ basic_json_parser_63:
             );
         }
 
+        /// escape tilde and slash
+        static std::string escape(std::string s)
+        {
+            // escape "~"" to "~0" and "/" to "~1"
+            replace_substring(s, "~", "~0");
+            replace_substring(s, "/", "~1");
+            return s;
+        }
+
+        /// unescape tilde and slash
+        static void unescape(std::string& s)
+        {
+            // first transform any occurrence of the sequence '~1' to '/'
+            replace_substring(s, "~1", "/");
+            // then transform any occurrence of the sequence '~0' to '~'
+            replace_substring(s, "~0", "~");
+        }
+
         /*!
         @param[in] reference_string  the reference string to the current value
         @param[in] value             the value to consider
@@ -9339,7 +9263,7 @@ basic_json_parser_63:
 
         @note Empty objects or arrays are flattened to `null`.
         */
-        static void flatten(const std::string reference_string,
+        static void flatten(const std::string& reference_string,
                             const basic_json& value,
                             basic_json& result)
         {
@@ -9376,12 +9300,7 @@ basic_json_parser_63:
                         // 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,
+                            flatten(reference_string + "/" + escape(element.first),
                                     element.second, result);
                         }
                     }
@@ -9435,13 +9354,128 @@ basic_json_parser_63:
         std::vector<std::string> reference_tokens {};
     };
 
-    ////////////////////////////
-    // JSON Pointer functions //
-    ////////////////////////////
+    //////////////////////////
+    // JSON Pointer support //
+    //////////////////////////
 
     /// @name JSON Pointer functions
     /// @{
 
+    /*!
+    @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. Similar to
+    @ref operator[](const typename object_t::key_type&), `null` values
+    are created in arrays and objects if necessary.
+
+    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 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.,operatorjson_pointer}
+
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer& ptr)
+    {
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @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  JSON pointer to the desired element
+
+    @return const 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.,operatorjson_pointer_const}
+
+    @since version 2.0.0
+    */
+    const_reference operator[](const json_pointer& ptr) const
+    {
+        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,
+    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}
+
+    @since version 2.0.0
+    */
+    reference at(const json_pointer& ptr)
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @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
+    {
+        return ptr.get_checked(this);
+    }
+
     /*!
     @brief return flattened JSON value
 
@@ -9505,45 +9539,146 @@ basic_json_parser_63:
 
     /// @}
 
+    //////////////////////////
+    // JSON Patch functions //
+    //////////////////////////
+
+    /// @name JSON Patch functions
+    /// @{
+
     /*!
     @brief applies a JSON patch
 
+    [JSON Patch](http://jsonpatch.com) defines a JSON document structure for
+    expressing a sequence of operations to apply to a JSON) document. With
+    this funcion, a JSON Patch is applied to the current JSON value by
+    executing all operations from the patch.
+
     @param[in] patch  JSON patch document
     @return patched document
 
-    @note The original JSON value is not changed; that is, the patch is
-          applied to a copy of the value.
+    @note The application of a patch is atomic: Either all operations succeed
+          and the patched document is returned or an exception is thrown. In
+          any case, the original value is not changed: the patch is applied
+          to a copy of the value.
 
-    @sa [RFC 6902](https://tools.ietf.org/html/rfc6902)
+    @throw std::out_of_range if a JSON pointer inside the patch could not
+    be resolved successfully in the current JSON value; example: `"key baz
+    not found"`
+    @throw invalid_argument if the JSON patch is malformed (e.g., mandatory
+    attributes are missing); example: `"operation add must have member path"`
+
+    @complexity Linear in the size of the JSON value and the length of the
+    JSON patch. As usually only a fraction of the JSON value is affected by
+    the patch, the complexity can usually be neglected.
+
+    @liveexample{The following code shows how a JSON patch is applied to a
+    value.,patch}
+
+    @sa @ref diff -- create a JSON patch by comparing two JSON values
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+    @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
+
+    @since version 2.0.0
     */
-    basic_json apply_patch(const basic_json& patch) const
+    basic_json patch(const basic_json& patch) const
     {
         // make a working copy to apply the patch to
         basic_json result = *this;
 
+        // the valid JSON Patch operations
+        enum class patch_operations {add, remove, replace, move, copy, test, invalid};
+
+        const auto get_op = [](const std::string op)
+        {
+            if (op == "add")
+            {
+                return patch_operations::add;
+            }
+            if (op == "remove")
+            {
+                return patch_operations::remove;
+            }
+            if (op == "replace")
+            {
+                return patch_operations::replace;
+            }
+            if (op == "move")
+            {
+                return patch_operations::move;
+            }
+            if (op == "copy")
+            {
+                return patch_operations::copy;
+            }
+            if (op == "test")
+            {
+                return patch_operations::test;
+            }
+
+            return patch_operations::invalid;
+        };
+
         // wrapper for "add" operation; add value at ptr
         const auto operation_add = [&result](json_pointer & ptr, basic_json val)
         {
-            // get reference to parent of JSON pointer ptr
-            const auto last_path = ptr.pop_back();
-            basic_json& parent = result.at(ptr);
-
-            if (parent.is_object())
+            // adding to the root of the target document means replacing it
+            if (ptr.is_root())
             {
-                // use operator[] to add value
-                parent[last_path] = val;
+                result = val;
             }
-            else if (parent.is_array())
+            else
             {
-                if (last_path == "-")
+                // make sure the top element of the pointer exists
+                json_pointer top_pointer = ptr.top();
+                if (top_pointer != ptr)
                 {
-                    // special case: append to back
-                    parent.push_back(val);
+                    basic_json& x = result.at(top_pointer);
                 }
-                else
+
+                // get reference to parent of JSON pointer ptr
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result[ptr];
+
+                switch (parent.m_type)
                 {
-                    // default case: insert add offset
-                    parent.insert(parent.begin() + std::stoi(last_path), val);
+                    case value_t::null:
+                    case value_t::object:
+                    {
+                        // use operator[] to add value
+                        parent[last_path] = val;
+                        break;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (last_path == "-")
+                        {
+                            // special case: append to back
+                            parent.push_back(val);
+                        }
+                        else
+                        {
+                            const auto idx = std::stoi(last_path);
+                            if (static_cast<size_type>(idx) > parent.size())
+                            {
+                                // avoid undefined behavior
+                                throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
+                            }
+                            else
+                            {
+                                // default case: insert add offset
+                                parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
+                            }
+                        }
+                        break;
+                    }
+
+                    default:
+                    {
+                        throw std::domain_error("unexpected parent type " + parent.type_name());
+                    }
                 }
             }
         };
@@ -9558,11 +9693,21 @@ basic_json_parser_63:
             // remove child
             if (parent.is_object())
             {
-                parent.erase(parent.find(last_path));
+                // perform range check
+                auto it = parent.find(last_path);
+                if (it != parent.end())
+                {
+                    parent.erase(it);
+                }
+                else
+                {
+                    throw std::out_of_range("key '" + last_path + "' not found");
+                }
             }
             else if (parent.is_array())
             {
-                parent.erase(parent.begin() + std::stoi(last_path));
+                // note erase performs range check
+                parent.erase(static_cast<size_type>(std::stoi(last_path)));
             }
         };
 
@@ -9570,7 +9715,7 @@ basic_json_parser_63:
         if (not patch.is_array())
         {
             // a JSON patch must be an array of objects
-            throw std::domain_error("JSON patch must be an array of objects");
+            throw std::invalid_argument("JSON patch must be an array of objects");
         }
 
         // iterate and apply th eoperations
@@ -9590,13 +9735,13 @@ basic_json_parser_63:
                 // check if desired value is present
                 if (it == val.m_value.object->end())
                 {
-                    throw std::domain_error(error_msg + " must have member '" + member + "'");
+                    throw std::invalid_argument(error_msg + " must have member '" + member + "'");
                 }
 
                 // check if result is of type string
                 if (string_type and not it->second.is_string())
                 {
-                    throw std::domain_error(error_msg + " must have string member '" + member + "'");
+                    throw std::invalid_argument(error_msg + " must have string member '" + member + "'");
                 }
 
                 // no error: return value
@@ -9606,7 +9751,7 @@ basic_json_parser_63:
             // type check
             if (not val.is_object())
             {
-                throw std::domain_error("JSON patch must be an array of objects");
+                throw std::invalid_argument("JSON patch must be an array of objects");
             }
 
             // collect mandatory members
@@ -9614,51 +9759,251 @@ basic_json_parser_63:
             const std::string path = get_value(op, "path", true);
             json_pointer ptr(path);
 
-            if (op == "add")
+            switch (get_op(op))
             {
-                operation_add(ptr, get_value("add", "value", false));
-            }
-            else if (op == "remove")
-            {
-                operation_remove(ptr);
-            }
-            else if (op == "replace")
-            {
-                result.at(ptr) = get_value("replace", "value", false);
-            }
-            else if (op == "move")
-            {
-                const std::string from_path = get_value("move", "from", true);
-                json_pointer from_ptr(from_path);
-                basic_json v = result[from_ptr];
-
-                operation_remove(from_ptr);
-                operation_add(ptr, v);
-            }
-            else if (op == "copy")
-            {
-                const std::string from_path = get_value("copy", "from", true);;
-                const json_pointer from_ptr(from_path);
-
-                result[ptr] = result.at(from_ptr);
-            }
-            else if (op == "test")
-            {
-                if (result.at(ptr) != get_value("test", "value", false))
+                case patch_operations::add:
                 {
-                    throw std::domain_error("unsuccessful: " + val.dump());
+                    operation_add(ptr, get_value("add", "value", false));
+                    break;
+                }
+
+                case patch_operations::remove:
+                {
+                    operation_remove(ptr);
+                    break;
+                }
+
+                case patch_operations::replace:
+                {
+                    // the "path" location must exist - use at()
+                    result.at(ptr) = get_value("replace", "value", false);
+                    break;
+                }
+
+                case patch_operations::move:
+                {
+                    const std::string from_path = get_value("move", "from", true);
+                    json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    basic_json v = result.at(from_ptr);
+
+                    // The move operation is functionally identical to a
+                    // "remove" operation on the "from" location, followed
+                    // immediately by an "add" operation at the target
+                    // location with the value that was just removed.
+                    operation_remove(from_ptr);
+                    operation_add(ptr, v);
+                    break;
+                }
+
+                case patch_operations::copy:
+                {
+                    const std::string from_path = get_value("copy", "from", true);;
+                    const json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    result[ptr] = result.at(from_ptr);
+                    break;
+                }
+
+                case patch_operations::test:
+                {
+                    bool success = false;
+                    try
+                    {
+                        // check if "value" matches the one at "path"
+                        // the "path" location must exist - use at()
+                        success = (result.at(ptr) == get_value("test", "value", false));
+                    }
+                    catch (std::out_of_range&)
+                    {
+                        // ignore out of range errors: success remains false
+                    }
+
+                    // throw an exception if test fails
+                    if (not success)
+                    {
+                        throw std::domain_error("unsuccessful: " + val.dump());
+                    }
+
+                    break;
+                }
+
+                case patch_operations::invalid:
+                {
+                    // op must be "add", "remove", "replace", "move", "copy", or
+                    // "test"
+                    throw std::invalid_argument("operation value '" + op + "' is invalid");
                 }
-            }
-            else
-            {
-                // op must be "add", "remove", "replace", "move", "copy", or
-                // "test"
-                throw std::domain_error("operation value '" + op + "' is invalid");
             }
         }
 
         return result;
     }
+
+    /*!
+    @brief creates a diff as a JSON patch
+
+    Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
+    be changed into the value @a target by calling @ref patch function.
+
+    @invariant For two JSON values @a source and @a target, the following code
+    yields always `true`:
+    @code {.cpp}
+    source.patch(diff(source, target)) == target;
+    @endcode
+
+    @note Currently, only `remove`, `add`, and `replace` operations are
+          generated.
+
+    @param[in] source  JSON value to copare from
+    @param[in] target  JSON value to copare against
+    @param[in] path    helper value to create JSON pointers
+
+    @return a JSON patch to convert the @a source to @a target
+
+    @complexity Linear in the lengths of @a source and @a target.
+
+    @liveexample{The following code shows how a JSON patch is created as a
+    diff for two JSON values.,diff}
+
+    @sa @ref patch -- apply a JSON patch
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+
+    @since version 2.0.0
+    */
+    static basic_json diff(const basic_json& source,
+                           const basic_json& target,
+                           std::string path = "") noexcept
+    {
+        // the patch
+        basic_json result(value_t::array);
+
+        // if the values are the same, return empty patch
+        if (source == target)
+        {
+            return result;
+        }
+
+        if (source.type() != target.type())
+        {
+            // different types: replace value
+            result.push_back(
+            {
+                {"op", "replace"},
+                {"path", path},
+                {"value", target}
+            });
+        }
+        else
+        {
+            switch (source.type())
+            {
+                case value_t::array:
+                {
+                    // first pass: traverse common elements
+                    size_t i = 0;
+                    while (i < source.size() and i < target.size())
+                    {
+                        // recursive call to compare array values at index i
+                        auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
+                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        ++i;
+                    }
+
+                    // i now reached the end of at least one array
+                    // in a second pass, traverse the remaining elements
+
+                    // remove my remaining elements
+                    while (i < source.size())
+                    {
+                        result.push_back(object(
+                        {
+                            {"op", "remove"},
+                            {"path", path + "/" + std::to_string(i)}
+                        }));
+                        ++i;
+                    }
+
+                    // add other remaining elements
+                    while (i < target.size())
+                    {
+                        result.push_back(
+                        {
+                            {"op", "add"},
+                            {"path", path + "/" + std::to_string(i)},
+                            {"value", target[i]}
+                        });
+                        ++i;
+                    }
+
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // first pass: traverse this object's elements
+                    for (auto it = source.begin(); it != source.end(); ++it)
+                    {
+                        // escape the key name to be used in a JSON patch
+                        const auto key = json_pointer::escape(it.key());
+
+                        if (target.find(it.key()) != target.end())
+                        {
+                            // recursive call to compare object values at key it
+                            auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
+                            result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        }
+                        else
+                        {
+                            // found a key that is not in o -> remove it
+                            result.push_back(object(
+                            {
+                                {"op", "remove"},
+                                {"path", path + "/" + key}
+                            }));
+                        }
+                    }
+
+                    // second pass: traverse other object's elements
+                    for (auto it = target.begin(); it != target.end(); ++it)
+                    {
+                        if (source.find(it.key()) == source.end())
+                        {
+                            // found a key that is not in this -> add it
+                            const auto key = json_pointer::escape(it.key());
+                            result.push_back(
+                            {
+                                {"op", "add"},
+                                {"path", path + "/" + key},
+                                {"value", it.value()}
+                            });
+                        }
+                    }
+
+                    break;
+                }
+
+                default:
+                {
+                    // both primitive type: replace value
+                    result.push_back(
+                    {
+                        {"op", "replace"},
+                        {"path", path},
+                        {"value", target}
+                    });
+                    break;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// @}
 };
 
 
@@ -9678,9 +10023,9 @@ using json = basic_json<>;
 }
 
 
-/////////////////////////
-// nonmember functions //
-/////////////////////////
+///////////////////////
+// nonmember support //
+///////////////////////
 
 // specialization of std::swap, and std::hash
 namespace std
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index af6df6ed..51b72167 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -3595,121 +3595,6 @@ class basic_json
         }
     }
 
-    /*!
-    @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. Similar to
-    @ref operator[](const typename object_t::key_type&), `null` values
-    are created in arrays and objects if necessary.
-
-    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 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.,operatorjson_pointer}
-
-    @since version 2.0.0
-    */
-    reference operator[](const json_pointer& ptr)
-    {
-        return ptr.get_unchecked(this);
-    }
-
-    /*!
-    @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  JSON pointer to the desired element
-
-    @return const 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.,operatorjson_pointer_const}
-
-    @since version 2.0.0
-    */
-    const_reference operator[](const json_pointer& ptr) const
-    {
-        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,
-    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}
-
-    @since version 2.0.0
-    */
-    reference at(const json_pointer& ptr)
-    {
-        return ptr.get_checked(this);
-    }
-
-    /*!
-    @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
-    {
-        return ptr.get_checked(this);
-    }
-
     /*!
     @brief access specified object element with default value
 
@@ -4145,8 +4030,8 @@ class basic_json
 
     @throw std::domain_error when called on a type other than JSON array;
     example: `"cannot use erase() with null"`
-    @throw std::out_of_range when `idx >= size()`; example: `"index out of
-    range"`
+    @throw std::out_of_range when `idx >= size()`; example: `"array index 17
+    is out of range"`
 
     @complexity Linear in distance between @a idx and the end of the container.
 
@@ -4167,7 +4052,7 @@ class basic_json
         {
             if (idx >= size())
             {
-                throw std::out_of_range("index out of range");
+                throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
             }
 
             assert(m_value.array != nullptr);
@@ -8279,9 +8164,17 @@ class basic_json
             : reference_tokens(split(s))
         {}
 
+        /// test for inequality
+        bool operator!=(const json_pointer& rhs) const
+        {
+            return reference_tokens != rhs.reference_tokens;
+        }
+
+      private:
+        /// remove and return last reference pointer
         std::string pop_back()
         {
-            if (reference_tokens.empty())
+            if (is_root())
             {
                 throw std::domain_error("JSON pointer has no parent");
             }
@@ -8291,7 +8184,24 @@ class basic_json
             return last;
         }
 
-      private:
+        /// return whether pointer points to the root document
+        bool is_root() const
+        {
+            return reference_tokens.empty();
+        }
+
+        json_pointer top() const
+        {
+            if (is_root())
+            {
+                throw std::domain_error("JSON pointer has no parent");
+            }
+
+            json_pointer result = *this;
+            result.reference_tokens = {reference_tokens[0]};
+            return result;
+        }
+
         /*!
         @brief create and return a reference to the pointed to value
         */
@@ -8330,7 +8240,7 @@ class basic_json
                     case value_t::array:
                     {
                         // create an entry in the array
-                        result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -8393,7 +8303,7 @@ class basic_json
                         else
                         {
                             // convert array index to number; unchecked access
-                            ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                            ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         }
                         break;
                     }
@@ -8438,7 +8348,7 @@ class basic_json
                         }
 
                         // note: at performs range check
-                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -8490,7 +8400,7 @@ class basic_json
                         }
 
                         // use unchecked array access
-                        ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -8534,7 +8444,7 @@ class basic_json
                         }
 
                         // note: at performs range check
-                        ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
+                        ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
                         break;
                     }
 
@@ -8601,12 +8511,8 @@ class basic_json
                     }
                 }
 
-                // 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", "~");
-
                 // finally, store the reference token
+                unescape(reference_token);
                 result.push_back(reference_token);
             }
 
@@ -8642,6 +8548,24 @@ class basic_json
             );
         }
 
+        /// escape tilde and slash
+        static std::string escape(std::string s)
+        {
+            // escape "~"" to "~0" and "/" to "~1"
+            replace_substring(s, "~", "~0");
+            replace_substring(s, "/", "~1");
+            return s;
+        }
+
+        /// unescape tilde and slash
+        static void unescape(std::string& s)
+        {
+            // first transform any occurrence of the sequence '~1' to '/'
+            replace_substring(s, "~1", "/");
+            // then transform any occurrence of the sequence '~0' to '~'
+            replace_substring(s, "~0", "~");
+        }
+
         /*!
         @param[in] reference_string  the reference string to the current value
         @param[in] value             the value to consider
@@ -8649,7 +8573,7 @@ class basic_json
 
         @note Empty objects or arrays are flattened to `null`.
         */
-        static void flatten(const std::string reference_string,
+        static void flatten(const std::string& reference_string,
                             const basic_json& value,
                             basic_json& result)
         {
@@ -8686,12 +8610,7 @@ class basic_json
                         // 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,
+                            flatten(reference_string + "/" + escape(element.first),
                                     element.second, result);
                         }
                     }
@@ -8745,13 +8664,128 @@ class basic_json
         std::vector<std::string> reference_tokens {};
     };
 
-    ////////////////////////////
-    // JSON Pointer functions //
-    ////////////////////////////
+    //////////////////////////
+    // JSON Pointer support //
+    //////////////////////////
 
     /// @name JSON Pointer functions
     /// @{
 
+    /*!
+    @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. Similar to
+    @ref operator[](const typename object_t::key_type&), `null` values
+    are created in arrays and objects if necessary.
+
+    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 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.,operatorjson_pointer}
+
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer& ptr)
+    {
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @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  JSON pointer to the desired element
+
+    @return const 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.,operatorjson_pointer_const}
+
+    @since version 2.0.0
+    */
+    const_reference operator[](const json_pointer& ptr) const
+    {
+        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,
+    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}
+
+    @since version 2.0.0
+    */
+    reference at(const json_pointer& ptr)
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @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
+    {
+        return ptr.get_checked(this);
+    }
+
     /*!
     @brief return flattened JSON value
 
@@ -8815,45 +8849,146 @@ class basic_json
 
     /// @}
 
+    //////////////////////////
+    // JSON Patch functions //
+    //////////////////////////
+
+    /// @name JSON Patch functions
+    /// @{
+
     /*!
     @brief applies a JSON patch
 
+    [JSON Patch](http://jsonpatch.com) defines a JSON document structure for
+    expressing a sequence of operations to apply to a JSON) document. With
+    this funcion, a JSON Patch is applied to the current JSON value by
+    executing all operations from the patch.
+
     @param[in] patch  JSON patch document
     @return patched document
 
-    @note The original JSON value is not changed; that is, the patch is
-          applied to a copy of the value.
+    @note The application of a patch is atomic: Either all operations succeed
+          and the patched document is returned or an exception is thrown. In
+          any case, the original value is not changed: the patch is applied
+          to a copy of the value.
 
-    @sa [RFC 6902](https://tools.ietf.org/html/rfc6902)
+    @throw std::out_of_range if a JSON pointer inside the patch could not
+    be resolved successfully in the current JSON value; example: `"key baz
+    not found"`
+    @throw invalid_argument if the JSON patch is malformed (e.g., mandatory
+    attributes are missing); example: `"operation add must have member path"`
+
+    @complexity Linear in the size of the JSON value and the length of the
+    JSON patch. As usually only a fraction of the JSON value is affected by
+    the patch, the complexity can usually be neglected.
+
+    @liveexample{The following code shows how a JSON patch is applied to a
+    value.,patch}
+
+    @sa @ref diff -- create a JSON patch by comparing two JSON values
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+    @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
+
+    @since version 2.0.0
     */
-    basic_json apply_patch(const basic_json& patch) const
+    basic_json patch(const basic_json& patch) const
     {
         // make a working copy to apply the patch to
         basic_json result = *this;
 
+        // the valid JSON Patch operations
+        enum class patch_operations {add, remove, replace, move, copy, test, invalid};
+
+        const auto get_op = [](const std::string op)
+        {
+            if (op == "add")
+            {
+                return patch_operations::add;
+            }
+            if (op == "remove")
+            {
+                return patch_operations::remove;
+            }
+            if (op == "replace")
+            {
+                return patch_operations::replace;
+            }
+            if (op == "move")
+            {
+                return patch_operations::move;
+            }
+            if (op == "copy")
+            {
+                return patch_operations::copy;
+            }
+            if (op == "test")
+            {
+                return patch_operations::test;
+            }
+
+            return patch_operations::invalid;
+        };
+
         // wrapper for "add" operation; add value at ptr
         const auto operation_add = [&result](json_pointer & ptr, basic_json val)
         {
-            // get reference to parent of JSON pointer ptr
-            const auto last_path = ptr.pop_back();
-            basic_json& parent = result.at(ptr);
-
-            if (parent.is_object())
+            // adding to the root of the target document means replacing it
+            if (ptr.is_root())
             {
-                // use operator[] to add value
-                parent[last_path] = val;
+                result = val;
             }
-            else if (parent.is_array())
+            else
             {
-                if (last_path == "-")
+                // make sure the top element of the pointer exists
+                json_pointer top_pointer = ptr.top();
+                if (top_pointer != ptr)
                 {
-                    // special case: append to back
-                    parent.push_back(val);
+                    basic_json& x = result.at(top_pointer);
                 }
-                else
+
+                // get reference to parent of JSON pointer ptr
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result[ptr];
+
+                switch (parent.m_type)
                 {
-                    // default case: insert add offset
-                    parent.insert(parent.begin() + std::stoi(last_path), val);
+                    case value_t::null:
+                    case value_t::object:
+                    {
+                        // use operator[] to add value
+                        parent[last_path] = val;
+                        break;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (last_path == "-")
+                        {
+                            // special case: append to back
+                            parent.push_back(val);
+                        }
+                        else
+                        {
+                            const auto idx = std::stoi(last_path);
+                            if (static_cast<size_type>(idx) > parent.size())
+                            {
+                                // avoid undefined behavior
+                                throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
+                            }
+                            else
+                            {
+                                // default case: insert add offset
+                                parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
+                            }
+                        }
+                        break;
+                    }
+
+                    default:
+                    {
+                        throw std::domain_error("unexpected parent type " + parent.type_name());
+                    }
                 }
             }
         };
@@ -8868,11 +9003,21 @@ class basic_json
             // remove child
             if (parent.is_object())
             {
-                parent.erase(parent.find(last_path));
+                // perform range check
+                auto it = parent.find(last_path);
+                if (it != parent.end())
+                {
+                    parent.erase(it);
+                }
+                else
+                {
+                    throw std::out_of_range("key '" + last_path + "' not found");
+                }
             }
             else if (parent.is_array())
             {
-                parent.erase(parent.begin() + std::stoi(last_path));
+                // note erase performs range check
+                parent.erase(static_cast<size_type>(std::stoi(last_path)));
             }
         };
 
@@ -8880,7 +9025,7 @@ class basic_json
         if (not patch.is_array())
         {
             // a JSON patch must be an array of objects
-            throw std::domain_error("JSON patch must be an array of objects");
+            throw std::invalid_argument("JSON patch must be an array of objects");
         }
 
         // iterate and apply th eoperations
@@ -8900,13 +9045,13 @@ class basic_json
                 // check if desired value is present
                 if (it == val.m_value.object->end())
                 {
-                    throw std::domain_error(error_msg + " must have member '" + member + "'");
+                    throw std::invalid_argument(error_msg + " must have member '" + member + "'");
                 }
 
                 // check if result is of type string
                 if (string_type and not it->second.is_string())
                 {
-                    throw std::domain_error(error_msg + " must have string member '" + member + "'");
+                    throw std::invalid_argument(error_msg + " must have string member '" + member + "'");
                 }
 
                 // no error: return value
@@ -8916,7 +9061,7 @@ class basic_json
             // type check
             if (not val.is_object())
             {
-                throw std::domain_error("JSON patch must be an array of objects");
+                throw std::invalid_argument("JSON patch must be an array of objects");
             }
 
             // collect mandatory members
@@ -8924,51 +9069,251 @@ class basic_json
             const std::string path = get_value(op, "path", true);
             json_pointer ptr(path);
 
-            if (op == "add")
+            switch (get_op(op))
             {
-                operation_add(ptr, get_value("add", "value", false));
-            }
-            else if (op == "remove")
-            {
-                operation_remove(ptr);
-            }
-            else if (op == "replace")
-            {
-                result.at(ptr) = get_value("replace", "value", false);
-            }
-            else if (op == "move")
-            {
-                const std::string from_path = get_value("move", "from", true);
-                json_pointer from_ptr(from_path);
-                basic_json v = result[from_ptr];
-
-                operation_remove(from_ptr);
-                operation_add(ptr, v);
-            }
-            else if (op == "copy")
-            {
-                const std::string from_path = get_value("copy", "from", true);;
-                const json_pointer from_ptr(from_path);
-
-                result[ptr] = result.at(from_ptr);
-            }
-            else if (op == "test")
-            {
-                if (result.at(ptr) != get_value("test", "value", false))
+                case patch_operations::add:
                 {
-                    throw std::domain_error("unsuccessful: " + val.dump());
+                    operation_add(ptr, get_value("add", "value", false));
+                    break;
+                }
+
+                case patch_operations::remove:
+                {
+                    operation_remove(ptr);
+                    break;
+                }
+
+                case patch_operations::replace:
+                {
+                    // the "path" location must exist - use at()
+                    result.at(ptr) = get_value("replace", "value", false);
+                    break;
+                }
+
+                case patch_operations::move:
+                {
+                    const std::string from_path = get_value("move", "from", true);
+                    json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    basic_json v = result.at(from_ptr);
+
+                    // The move operation is functionally identical to a
+                    // "remove" operation on the "from" location, followed
+                    // immediately by an "add" operation at the target
+                    // location with the value that was just removed.
+                    operation_remove(from_ptr);
+                    operation_add(ptr, v);
+                    break;
+                }
+
+                case patch_operations::copy:
+                {
+                    const std::string from_path = get_value("copy", "from", true);;
+                    const json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    result[ptr] = result.at(from_ptr);
+                    break;
+                }
+
+                case patch_operations::test:
+                {
+                    bool success = false;
+                    try
+                    {
+                        // check if "value" matches the one at "path"
+                        // the "path" location must exist - use at()
+                        success = (result.at(ptr) == get_value("test", "value", false));
+                    }
+                    catch (std::out_of_range&)
+                    {
+                        // ignore out of range errors: success remains false
+                    }
+
+                    // throw an exception if test fails
+                    if (not success)
+                    {
+                        throw std::domain_error("unsuccessful: " + val.dump());
+                    }
+
+                    break;
+                }
+
+                case patch_operations::invalid:
+                {
+                    // op must be "add", "remove", "replace", "move", "copy", or
+                    // "test"
+                    throw std::invalid_argument("operation value '" + op + "' is invalid");
                 }
-            }
-            else
-            {
-                // op must be "add", "remove", "replace", "move", "copy", or
-                // "test"
-                throw std::domain_error("operation value '" + op + "' is invalid");
             }
         }
 
         return result;
     }
+
+    /*!
+    @brief creates a diff as a JSON patch
+
+    Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
+    be changed into the value @a target by calling @ref patch function.
+
+    @invariant For two JSON values @a source and @a target, the following code
+    yields always `true`:
+    @code {.cpp}
+    source.patch(diff(source, target)) == target;
+    @endcode
+
+    @note Currently, only `remove`, `add`, and `replace` operations are
+          generated.
+
+    @param[in] source  JSON value to copare from
+    @param[in] target  JSON value to copare against
+    @param[in] path    helper value to create JSON pointers
+
+    @return a JSON patch to convert the @a source to @a target
+
+    @complexity Linear in the lengths of @a source and @a target.
+
+    @liveexample{The following code shows how a JSON patch is created as a
+    diff for two JSON values.,diff}
+
+    @sa @ref patch -- apply a JSON patch
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+
+    @since version 2.0.0
+    */
+    static basic_json diff(const basic_json& source,
+                           const basic_json& target,
+                           std::string path = "") noexcept
+    {
+        // the patch
+        basic_json result(value_t::array);
+
+        // if the values are the same, return empty patch
+        if (source == target)
+        {
+            return result;
+        }
+
+        if (source.type() != target.type())
+        {
+            // different types: replace value
+            result.push_back(
+            {
+                {"op", "replace"},
+                {"path", path},
+                {"value", target}
+            });
+        }
+        else
+        {
+            switch (source.type())
+            {
+                case value_t::array:
+                {
+                    // first pass: traverse common elements
+                    size_t i = 0;
+                    while (i < source.size() and i < target.size())
+                    {
+                        // recursive call to compare array values at index i
+                        auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
+                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        ++i;
+                    }
+
+                    // i now reached the end of at least one array
+                    // in a second pass, traverse the remaining elements
+
+                    // remove my remaining elements
+                    while (i < source.size())
+                    {
+                        result.push_back(object(
+                        {
+                            {"op", "remove"},
+                            {"path", path + "/" + std::to_string(i)}
+                        }));
+                        ++i;
+                    }
+
+                    // add other remaining elements
+                    while (i < target.size())
+                    {
+                        result.push_back(
+                        {
+                            {"op", "add"},
+                            {"path", path + "/" + std::to_string(i)},
+                            {"value", target[i]}
+                        });
+                        ++i;
+                    }
+
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // first pass: traverse this object's elements
+                    for (auto it = source.begin(); it != source.end(); ++it)
+                    {
+                        // escape the key name to be used in a JSON patch
+                        const auto key = json_pointer::escape(it.key());
+
+                        if (target.find(it.key()) != target.end())
+                        {
+                            // recursive call to compare object values at key it
+                            auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
+                            result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        }
+                        else
+                        {
+                            // found a key that is not in o -> remove it
+                            result.push_back(object(
+                            {
+                                {"op", "remove"},
+                                {"path", path + "/" + key}
+                            }));
+                        }
+                    }
+
+                    // second pass: traverse other object's elements
+                    for (auto it = target.begin(); it != target.end(); ++it)
+                    {
+                        if (source.find(it.key()) == source.end())
+                        {
+                            // found a key that is not in this -> add it
+                            const auto key = json_pointer::escape(it.key());
+                            result.push_back(
+                            {
+                                {"op", "add"},
+                                {"path", path + "/" + key},
+                                {"value", it.value()}
+                            });
+                        }
+                    }
+
+                    break;
+                }
+
+                default:
+                {
+                    // both primitive type: replace value
+                    result.push_back(
+                    {
+                        {"op", "replace"},
+                        {"path", path},
+                        {"value", target}
+                    });
+                    break;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// @}
 };
 
 
@@ -8988,9 +9333,9 @@ using json = basic_json<>;
 }
 
 
-/////////////////////////
-// nonmember functions //
-/////////////////////////
+///////////////////////
+// nonmember support //
+///////////////////////
 
 // specialization of std::swap, and std::hash
 namespace std
diff --git a/test/unit.cpp b/test/unit.cpp
index 664648cb..f6c49883 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -3455,7 +3455,7 @@ TEST_CASE("element access")
                 {
                     json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                     CHECK_THROWS_AS(jarray.erase(8), std::out_of_range);
-                    CHECK_THROWS_WITH(jarray.erase(8), "index out of range");
+                    CHECK_THROWS_WITH(jarray.erase(8), "array index 8 is out of range");
                 }
             }
 
@@ -12395,304 +12395,384 @@ TEST_CASE("JSON patch")
 {
     SECTION("examples from RFC 6902")
     {
-        SECTION("example A.1 - Adding an Object Member")
+        SECTION("4. Operations")
         {
-            // An example target JSON document:
-            json doc = R"(
-                { "foo": "bar"}
-            )"_json;
+            // the ordering of members in JSON objects is not significant:
+            json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json;
+            json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json;
+            json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json;
 
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "add", "path": "/baz", "value": "qux" }
-                ]
-            )"_json;
-
-            // The resulting JSON document:
-            json expected = R"(
-                {
-                    "baz": "qux",
-                    "foo": "bar"
-                }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            // check if the operation objects are equivalent
+            CHECK(op1 == op2);
+            CHECK(op1 == op3);
         }
 
-        SECTION("example A.2 - Adding an Array Element")
+        SECTION("4.1 add")
         {
-            // An example target JSON document:
-            json doc = R"(
-                { "foo": [ "bar", "baz" ] }
-            )"_json;
+            json patch = R"([{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }])"_json;
 
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "add", "path": "/foo/1", "value": "qux" }
-                ]
-            )"_json;
+            // However, the object itself or an array containing it does need
+            // to exist, and it remains an error for that not to be the case.
+            // For example, an "add" with a target location of "/a/b" starting
+            // with this document
+            json doc1 = R"({ "a": { "foo": 1 } })"_json;
 
-            // The resulting JSON document:
-            json expected = R"(
-                { "foo": [ "bar", "qux", "baz" ] }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
-        }
-
-        SECTION("example A.3 - Removing an Object Member")
-        {
-            // An example target JSON document:
-            json doc = R"(
+            // is not an error, because "a" exists, and "b" will be added to
+            // its value.
+            CHECK_NOTHROW(doc1.patch(patch));
+            CHECK(doc1.patch(patch) == R"(
                 {
-                    "baz": "qux",
-                    "foo": "bar"
-                }
-            )"_json;
-
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "remove", "path": "/baz" }
-                ]
-            )"_json;
-
-            // The resulting JSON document:
-            json expected = R"(
-                { "foo": "bar" }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
-        }
-
-        SECTION("example A.4 - Removing an Array Element")
-        {
-            // An example target JSON document:
-            json doc = R"(
-                { "foo": [ "bar", "qux", "baz" ] }
-            )"_json;
-
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "remove", "path": "/foo/1" }
-                ]
-            )"_json;
-
-            // The resulting JSON document:
-            json expected = R"(
-                { "foo": [ "bar", "baz" ] }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
-        }
-
-        SECTION("example A.5 - Replacing a Value")
-        {
-            // An example target JSON document:
-            json doc = R"(
-                {
-                    "baz": "qux",
-                    "foo": "bar"
-                }
-            )"_json;
-
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "replace", "path": "/baz", "value": "boo" }
-                ]
-            )"_json;
-
-            json expected = R"(
-                {
-                    "baz": "boo",
-                    "foo": "bar"
-                }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
-        }
-
-        SECTION("example A.6 - Moving a Value")
-        {
-            // An example target JSON document:
-            json doc = R"(
-                {
-                    "foo": {
-                       "bar": "baz",
-                        "waldo": "fred"
-                    },
-                    "qux": {
-                        "corge": "grault"
+                    "a": {
+                        "foo": 1,
+                        "b": {
+                            "c": [ "foo", "bar" ]
+                        }
                     }
                 }
-            )"_json;
+            )"_json);
+
+            // It is an error in this document:
+            json doc2 = R"({ "q": { "bar": 2 } })"_json;
+
+            // because "a" does not exist.
+            CHECK_THROWS_AS(doc2.patch(patch), std::out_of_range);
+            CHECK_THROWS_WITH(doc2.patch(patch), "key 'a' not found");
+        }
+
+        SECTION("4.2 remove")
+        {
+            // If removing an element from an array, any elements above the
+            // specified index are shifted one position to the left.
+            json doc = {1, 2, 3, 4};
+            json patch = {{{"op", "remove"}, {"path", "/1"}}};
+            CHECK(doc.patch(patch) == json({1, 3, 4}));
+        }
+
+        SECTION("A.1. Adding an Object Member")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    { "foo": "bar"}
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
-                ]
-            )"_json;
+                    [
+                        { "op": "add", "path": "/baz", "value": "qux" }
+                    ]
+                )"_json;
 
             // The resulting JSON document:
             json expected = R"(
-                {
-                    "foo": {
-                       "bar": "baz"
-                    },
-                    "qux": {
-                        "corge": "grault",
-                        "thud": "fred"
+                    {
+                        "baz": "qux",
+                        "foo": "bar"
                     }
-                }
-            )"_json;
+                )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
         }
 
-        SECTION("example A.7 - Moving a Value")
+        SECTION("A.2. Adding an Array Element")
         {
             // An example target JSON document:
             json doc = R"(
-                { "foo": [ "all", "grass", "cows", "eat" ] }
-            )"_json;
+                    { "foo": [ "bar", "baz" ] }
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    { "op": "move", "from": "/foo/1", "path": "/foo/3" }
-                ]
-            )"_json;
+                    [
+                        { "op": "add", "path": "/foo/1", "value": "qux" }
+                    ]
+                )"_json;
 
             // The resulting JSON document:
             json expected = R"(
-                { "foo": [ "all", "cows", "eat", "grass" ] }
-            )"_json;
+                    { "foo": [ "bar", "qux", "baz" ] }
+                )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
         }
 
-        SECTION("example A.8 - Testing a Value: Success")
+        SECTION("A.3. Removing an Object Member")
         {
             // An example target JSON document:
             json doc = R"(
-                {
-                     "baz": "qux",
-                     "foo": [ "a", 2, "c" ]
-                }
-            )"_json;
+                    {
+                        "baz": "qux",
+                        "foo": "bar"
+                    }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "remove", "path": "/baz" }
+                    ]
+                )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                    { "foo": "bar" }
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.4. Removing an Array Element")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    { "foo": [ "bar", "qux", "baz" ] }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "remove", "path": "/foo/1" }
+                    ]
+                )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                    { "foo": [ "bar", "baz" ] }
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.5. Replacing a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    {
+                        "baz": "qux",
+                        "foo": "bar"
+                    }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "replace", "path": "/baz", "value": "boo" }
+                    ]
+                )"_json;
+
+            json expected = R"(
+                    {
+                        "baz": "boo",
+                        "foo": "bar"
+                    }
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.6. Moving a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    {
+                        "foo": {
+                           "bar": "baz",
+                            "waldo": "fred"
+                        },
+                        "qux": {
+                            "corge": "grault"
+                        }
+                    }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
+                    ]
+                )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                    {
+                        "foo": {
+                           "bar": "baz"
+                        },
+                        "qux": {
+                            "corge": "grault",
+                            "thud": "fred"
+                        }
+                    }
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.7. Moving a Value")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    { "foo": [ "all", "grass", "cows", "eat" ] }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "move", "from": "/foo/1", "path": "/foo/3" }
+                    ]
+                )"_json;
+
+            // The resulting JSON document:
+            json expected = R"(
+                    { "foo": [ "all", "cows", "eat", "grass" ] }
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.8. Testing a Value: Success")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    {
+                         "baz": "qux",
+                         "foo": [ "a", 2, "c" ]
+                    }
+                )"_json;
 
             // A JSON Patch document that will result in successful evaluation:
             json patch = R"(
-                [
-                    { "op": "test", "path": "/baz", "value": "qux" },
-                    { "op": "test", "path": "/foo/1", "value": 2 }
-                ]
-            )"_json;
+                    [
+                        { "op": "test", "path": "/baz", "value": "qux" },
+                        { "op": "test", "path": "/foo/1", "value": 2 }
+                    ]
+                )"_json;
 
             // check if evaluation does not throw
-            CHECK_NOTHROW(doc.apply_patch(patch));
+            CHECK_NOTHROW(doc.patch(patch));
             // check if patched document is unchanged
-            CHECK(doc.apply_patch(patch) == doc);
+            CHECK(doc.patch(patch) == doc);
         }
 
-        SECTION("example A.9 - Testing a Value: Error")
+        SECTION("A.9. Testing a Value: Error")
         {
             // An example target JSON document:
             json doc = R"(
-                { "baz": "qux" }
-            )"_json;
+                    { "baz": "qux" }
+                )"_json;
 
             // A JSON Patch document that will result in an error condition:
             json patch = R"(
-                [
-                    { "op": "test", "path": "/baz", "value": "bar" }
-                ]
-            )"_json;
+                    [
+                        { "op": "test", "path": "/baz", "value": "bar" }
+                    ]
+                )"_json;
 
             // check that evaluation throws
-            CHECK_THROWS_AS(doc.apply_patch(patch), std::domain_error);
-            CHECK_THROWS_WITH(doc.apply_patch(patch), "unsuccessful: " + patch[0].dump());
+            CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
+            CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
         }
 
-        SECTION("example A.10 - Adding a Nested Member Object")
+        SECTION("A.10. Adding a Nested Member Object")
         {
             // An example target JSON document:
             json doc = R"(
-                { "foo": "bar" }
-            )"_json;
+                    { "foo": "bar" }
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    { "op": "add", "path": "/child", "value": { "grandchild": { } } }
-                ]
-            )"_json;
+                    [
+                        { "op": "add", "path": "/child", "value": { "grandchild": { } } }
+                    ]
+                )"_json;
 
             // The resulting JSON document:
-            json expected = R"(
-            {
-                "foo": "bar",
-                "child": {
-                    "grandchild": {
-                    }
-                }
-            }
-            )"_json;
-
-            // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
-        }
-
-        SECTION("example A.11 - Ignoring Unrecognized Elements")
-        {
-            // An example target JSON document:
-            json doc = R"(
-                { "foo": "bar" }
-            )"_json;
-
-            // A JSON Patch document:
-            json patch = R"(
-                [
-                    { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
-                ]
-            )"_json;
-
             json expected = R"(
                 {
                     "foo": "bar",
-                    "baz": "qux"
-                } 
-            )"_json;
+                    "child": {
+                        "grandchild": {
+                        }
+                    }
+                }
+                )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
         }
 
-        SECTION("example A.12 - Adding to a Nonexistent Target")
+        SECTION("A.11. Ignoring Unrecognized Elements")
         {
             // An example target JSON document:
             json doc = R"(
-                { "foo": "bar" }
-            )"_json;
+                    { "foo": "bar" }
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    { "op": "add", "path": "/baz/bat", "value": "qux" }
-                ]
-            )"_json;
+                    [
+                        { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
+                    ]
+                )"_json;
+
+            json expected = R"(
+                    {
+                        "foo": "bar",
+                        "baz": "qux"
+                    } 
+                )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("A.12. Adding to a Nonexistent Target")
+        {
+            // An example target JSON document:
+            json doc = R"(
+                    { "foo": "bar" }
+                )"_json;
+
+            // A JSON Patch document:
+            json patch = R"(
+                    [
+                        { "op": "add", "path": "/baz/bat", "value": "qux" }
+                    ]
+                )"_json;
 
             // This JSON Patch document, applied to the target JSON document
             // above, would result in an error (therefore, it would not be
@@ -12700,89 +12780,150 @@ TEST_CASE("JSON patch")
             // references neither the root of the document, nor a member of
             // an existing object, nor a member of an existing array.
 
-            CHECK_THROWS_AS(doc.apply_patch(patch), std::out_of_range);
-            CHECK_THROWS_WITH(doc.apply_patch(patch), "key 'baz' not found");
+            CHECK_THROWS_AS(doc.patch(patch), std::out_of_range);
+            CHECK_THROWS_WITH(doc.patch(patch), "key 'baz' not found");
         }
 
-        // A.13.  Invalid JSON Patch Document
+        // A.13. Invalid JSON Patch Document
         // not applicable
 
-        SECTION("example A.14 - Escape Ordering")
+        SECTION("A.14. Escape Ordering")
         {
             // An example target JSON document:
             json doc = R"(
-                {
-                    "/": 9,
-                    "~1": 10
-                }
-            )"_json;
+                    {
+                        "/": 9,
+                        "~1": 10
+                    }
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    {"op": "test", "path": "/~01", "value": 10}
-                ]
-            )"_json;
+                    [
+                        {"op": "test", "path": "/~01", "value": 10}
+                    ]
+                )"_json;
 
             json expected = R"(
-                {
-                    "/": 9,
-                    "~1": 10
-                } 
-            )"_json;
+                    {
+                        "/": 9,
+                        "~1": 10
+                    } 
+                )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
         }
 
-        SECTION("example A.15 - Comparing Strings and Numbers")
+        SECTION("A.15. Comparing Strings and Numbers")
         {
             // An example target JSON document:
             json doc = R"(
-                {
-                    "/": 9,
-                    "~1": 10
-                } 
-            )"_json;
+                    {
+                        "/": 9,
+                        "~1": 10
+                    } 
+                )"_json;
 
             // A JSON Patch document that will result in an error condition:
             json patch = R"(
-                [
-                    {"op": "test", "path": "/~01", "value": "10"}
-                ]
-            )"_json;
+                    [
+                        {"op": "test", "path": "/~01", "value": "10"}
+                    ]
+                )"_json;
 
             // check that evaluation throws
-            CHECK_THROWS_AS(doc.apply_patch(patch), std::domain_error);
-            CHECK_THROWS_WITH(doc.apply_patch(patch), "unsuccessful: " + patch[0].dump());
+            CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
+            CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
         }
 
-        SECTION("example A.16 - Adding an Array Value")
+        SECTION("A.16. Adding an Array Value")
         {
             // An example target JSON document:
             json doc = R"(
-                { "foo": ["bar"] }
-            )"_json;
+                    { "foo": ["bar"] }
+                )"_json;
 
             // A JSON Patch document:
             json patch = R"(
-                [
-                    { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
-                ]
-            )"_json;
+                    [
+                        { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
+                    ]
+                )"_json;
 
             // The resulting JSON document:
             json expected = R"(
-                { "foo": ["bar", ["abc", "def"]] }
-            )"_json;
+                    { "foo": ["bar", ["abc", "def"]] }
+                )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
         }
     }
 
     SECTION("own examples")
     {
+        SECTION("add")
+        {
+            SECTION("add to the root element")
+            {
+                // If the path is the root of the target document - the
+                // specified value becomes the entire content of the target
+                // document.
+
+                // An example target JSON document:
+                json doc = 17;
+
+                // A JSON Patch document:
+                json patch = R"(
+                        [
+                            { "op": "add", "path": "", "value": [1,2,3] }
+                        ]
+                    )"_json;
+
+                // The resulting JSON document:
+                json expected = {1, 2, 3};
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == expected);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, expected)) == expected);
+            }
+
+            SECTION("add to end of the array")
+            {
+                // The specified index MUST NOT be greater than the number of
+                // elements in the array. The example below uses and index of
+                // exactly the number of elements in the array which is legal.
+
+                // An example target JSON document:
+                json doc = {0, 1, 2};
+
+                // A JSON Patch document:
+                json patch = R"(
+                    [
+                        { "op": "add", "path": "/3", "value": 3 }
+                    ]
+                )"_json;
+
+                // The resulting JSON document:
+                json expected = {0, 1, 2, 3};
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == expected);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, expected)) == expected);
+            }
+        }
+
         SECTION("copy")
         {
             // An example target JSON document:
@@ -12805,6 +12946,7 @@ TEST_CASE("JSON patch")
                 ]
             )"_json;
 
+            // The resulting JSON document:
             json expected = R"(
                 {
                     "foo": {
@@ -12819,7 +12961,60 @@ TEST_CASE("JSON patch")
             )"_json;
 
             // check if patched value is as expected
-            CHECK(doc.apply_patch(patch) == expected);
+            CHECK(doc.patch(patch) == expected);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, expected)) == expected);
+        }
+
+        SECTION("replace")
+        {
+            json j = "string";
+            json patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}};
+            CHECK(j.patch(patch) == json(1));
+        }
+
+        SECTION("documentation GIF")
+        {
+            {
+                // a JSON patch
+                json p1 = R"(
+                     [{"op": "add", "path": "/GB", "value": "London"}]
+                    )"_json;
+
+                // a JSON value
+                json source = R"(
+                      {"D": "Berlin", "F": "Paris"}
+                    )"_json;
+
+                // apply the patch
+                json target = source.patch(p1);
+                // target = { "D": "Berlin", "F": "Paris", "GB": "London" }
+                CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json);
+
+                // create a diff from two JSONs
+                json p2 = json::diff(target, source);
+                // p2 = [{"op": "delete", "path": "/GB"}]
+                CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json);
+            }
+            {
+                // a JSON value
+                json j = {"good", "bad", "ugly"};
+
+                // a JSON pointer
+                auto ptr = json::json_pointer("/2");
+
+                // use to access elements
+                j[ptr] = {{"it", "cattivo"}};
+                CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json);
+
+                // use user-defined string literal
+                j["/2/en"_json_pointer] = "ugly";
+                CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json);
+
+                json flat = j.flatten();
+                CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json);
+            }
         }
     }
 
@@ -12827,20 +13022,44 @@ TEST_CASE("JSON patch")
     {
         SECTION("unknown operation")
         {
+            SECTION("not an array")
+            {
+                json j;
+                json patch = {{"op", "add"}, {"path", ""}, {"value", 1}};
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects");
+            }
+
+            SECTION("not an array of objects")
+            {
+                json j;
+                json patch = {"op", "add", "path", "", "value", 1};
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects");
+            }
+
             SECTION("missing 'op'")
             {
                 json j;
                 json patch = {{{"foo", "bar"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have member 'op'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation must have member 'op'");
             }
 
             SECTION("non-string 'op'")
             {
                 json j;
                 json patch = {{{"op", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have string member 'op'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation must have string member 'op'");
+            }
+
+            SECTION("invalid operation")
+            {
+                json j;
+                json patch = {{{"op", "foo"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation value 'foo' is invalid");
             }
         }
 
@@ -12850,24 +13069,32 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "add"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "add"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have string member 'path'");
             }
 
             SECTION("missing 'value'")
             {
                 json j;
                 json patch = {{{"op", "add"}, {"path", ""}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'value'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'value'");
+            }
+
+            SECTION("invalid array index")
+            {
+                json j = {1, 2};
+                json patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "array index 4 is out of range");
             }
         }
 
@@ -12877,16 +13104,40 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "remove"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "remove"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have string member 'path'");
+            }
+
+            SECTION("nonexisting target location (array)")
+            {
+                json j = {1, 2, 3};
+                json patch = {{{"op", "remove"}, {"path", "/17"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range");
+            }
+
+            SECTION("nonexisting target location (object)")
+            {
+                json j = {{"foo", 1}, {"bar", 2}};
+                json patch = {{{"op", "remove"}, {"path", "/baz"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
+            }
+
+            SECTION("root element as target location")
+            {
+                json j = "string";
+                json patch = {{{"op", "remove"}, {"path", ""}}};
+                CHECK_THROWS_AS(j.patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(j.patch(patch), "JSON pointer has no parent");
             }
         }
 
@@ -12896,24 +13147,40 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "replace"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "replace"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have string member 'path'");
             }
 
             SECTION("missing 'value'")
             {
                 json j;
                 json patch = {{{"op", "replace"}, {"path", ""}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'value'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'value'");
+            }
+
+            SECTION("nonexisting target location (array)")
+            {
+                json j = {1, 2, 3};
+                json patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range");
+            }
+
+            SECTION("nonexisting target location (object)")
+            {
+                json j = {{"foo", 1}, {"bar", 2}};
+                json patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
             }
         }
 
@@ -12923,32 +13190,48 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "move"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "move"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'path'");
             }
 
             SECTION("missing 'from'")
             {
                 json j;
                 json patch = {{{"op", "move"}, {"path", ""}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'from'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'from'");
             }
 
             SECTION("non-string 'from'")
             {
                 json j;
                 json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'from'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'from'");
+            }
+
+            SECTION("nonexisting from location (array)")
+            {
+                json j = {1, 2, 3};
+                json patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range");
+            }
+
+            SECTION("nonexisting from location (object)")
+            {
+                json j = {{"foo", 1}, {"bar", 2}};
+                json patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
             }
         }
 
@@ -12958,32 +13241,48 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "copy"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "copy"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'path'");
             }
 
             SECTION("missing 'from'")
             {
                 json j;
                 json patch = {{{"op", "copy"}, {"path", ""}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'from'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'from'");
             }
 
             SECTION("non-string 'from'")
             {
                 json j;
                 json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'from'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'from'");
+            }
+
+            SECTION("nonexisting from location (array)")
+            {
+                json j = {1, 2, 3};
+                json patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range");
+            }
+
+            SECTION("nonexisting from location (object)")
+            {
+                json j = {{"foo", 1}, {"bar", 2}};
+                json patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}};
+                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
+                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
             }
         }
 
@@ -12993,27 +13292,288 @@ TEST_CASE("JSON patch")
             {
                 json j;
                 json patch = {{{"op", "test"}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'path'");
             }
 
             SECTION("non-string 'path'")
             {
                 json j;
                 json patch = {{{"op", "test"}, {"path", 1}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have string member 'path'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have string member 'path'");
             }
 
             SECTION("missing 'value'")
             {
                 json j;
                 json patch = {{{"op", "test"}, {"path", ""}}};
-                CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
-                CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'value'");
+                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
+                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'value'");
             }
         }
     }
+
+    SECTION("Examples from jsonpatch.com")
+    {
+        SECTION("Simple Example")
+        {
+            // The original document
+            json doc = R"(
+                {
+                  "baz": "qux",
+                  "foo": "bar"
+                }
+            )"_json;
+
+            // The patch
+            json patch = R"(
+                [
+                  { "op": "replace", "path": "/baz", "value": "boo" },
+                  { "op": "add", "path": "/hello", "value": ["world"] },
+                  { "op": "remove", "path": "/foo"}
+                ]
+            )"_json;
+
+            // The result
+            json result = R"(
+                {
+                   "baz": "boo",
+                   "hello": ["world"]
+                }
+            )"_json;
+
+            // check if patched value is as expected
+            CHECK(doc.patch(patch) == result);
+
+            // check roundtrip
+            CHECK(doc.patch(json::diff(doc, result)) == result);
+        }
+
+        SECTION("Operations")
+        {
+            // The original document
+            json doc = R"(
+                {
+                  "biscuits": [
+                    {"name":"Digestive"},
+                    {"name": "Choco Liebniz"}
+                  ]
+                }
+            )"_json;
+
+            SECTION("add")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}}
+                    ]
+                )"_json;
+
+                // The result
+                json result = R"(
+                    {
+                      "biscuits": [
+                        {"name": "Digestive"},
+                        {"name": "Ginger Nut"},
+                        {"name": "Choco Liebniz"}
+                      ]
+                    }
+                )"_json;
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == result);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, result)) == result);
+            }
+
+            SECTION("remove")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "remove", "path": "/biscuits"}
+                    ]
+                )"_json;
+
+                // The result
+                json result = R"(
+                    {}
+                )"_json;
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == result);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, result)) == result);
+            }
+
+            SECTION("replace")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"}
+                    ]
+                )"_json;
+
+                // The result
+                json result = R"(
+                    {
+                      "biscuits": [
+                        {"name": "Chocolate Digestive"},
+                        {"name": "Choco Liebniz"}
+                      ]
+                    }
+                )"_json;
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == result);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, result)) == result);
+            }
+
+            SECTION("copy")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"}
+                    ]
+                )"_json;
+
+                // The result
+                json result = R"(
+                    {
+                      "biscuits": [
+                        {"name": "Digestive"},
+                        {"name": "Choco Liebniz"}
+                      ],
+                      "best_biscuit": {
+                        "name": "Digestive"
+                      }
+                    }
+                )"_json;
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == result);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, result)) == result);
+            }
+
+            SECTION("move")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "move", "from": "/biscuits", "path": "/cookies"}
+                    ]
+                )"_json;
+
+                // The result
+                json result = R"(
+                    {
+                      "cookies": [
+                        {"name": "Digestive"},
+                        {"name": "Choco Liebniz"}
+                      ]
+                    }
+                )"_json;
+
+                // check if patched value is as expected
+                CHECK(doc.patch(patch) == result);
+
+                // check roundtrip
+                CHECK(doc.patch(json::diff(doc, result)) == result);
+            }
+
+            SECTION("test")
+            {
+                // The patch
+                json patch = R"(
+                    [
+                        {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"}
+                    ]
+                )"_json;
+
+                // the test will fail
+                CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
+                CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
+            }
+        }
+    }
+
+    SECTION("Examples from bruth.github.io/jsonpatch-js")
+    {
+        SECTION("add")
+        {
+            CHECK(R"( {} )"_json.patch(
+                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
+                  ) == R"( {"foo": "bar"} )"_json);
+
+            CHECK(R"( {"foo": [1, 3]} )"_json.patch(
+                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
+                  ) == R"( {"foo": "bar"} )"_json);
+
+            CHECK(R"( {"foo": [{}]} )"_json.patch(
+                      R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json
+                  ) == R"( {"foo": [{"bar": "baz"}]} )"_json);
+        }
+
+        SECTION("remove")
+        {
+            CHECK(R"( {"foo": "bar"} )"_json.patch(
+                      R"( [{"op": "remove", "path": "/foo"}] )"_json
+                  ) == R"( {} )"_json);
+
+            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+                      R"( [{"op": "remove", "path": "/foo/1"}] )"_json
+                  ) == R"( {"foo": [1, 3]} )"_json);
+
+            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
+                      R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json
+                  ) == R"( {"foo": [{}]} )"_json);
+        }
+
+        SECTION("replace")
+        {
+            CHECK(R"( {"foo": "bar"} )"_json.patch(
+                      R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json
+                  ) == R"( {"foo": 1} )"_json);
+
+            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+                      R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json
+                  ) == R"( {"foo": [1, 4, 3]} )"_json);
+
+            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
+                      R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json
+                  ) == R"( {"foo": [{"bar": 1}]} )"_json);
+        }
+
+        SECTION("move")
+        {
+            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+                      R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json
+                  ) == R"( {"bar": [1, 2, 3]} )"_json);
+        }
+
+        SECTION("copy")
+        {
+            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+                      R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json
+                  ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json);
+        }
+
+        SECTION("copy")
+        {
+            CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch(
+                              R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json));
+        }
+    }
 }
 
 TEST_CASE("regression tests")

From 1d3b4dd1583d1eeacf7df3c0f9ad2f356b378712 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 00:03:47 +0200
Subject: [PATCH 10/19] added test cases

---
 ChangeLog.md                                |  5 ++-
 doc/examples/json_pointer__to_string.cpp    | 34 +++++++++++++++++
 doc/examples/json_pointer__to_string.link   |  1 +
 doc/examples/json_pointer__to_string.output | 12 ++++++
 src/json.hpp                                | 37 +++++++++++++++++--
 src/json.hpp.re2c                           | 37 +++++++++++++++++--
 test/unit.cpp                               | 41 +++++++++++++++++++++
 7 files changed, 160 insertions(+), 7 deletions(-)
 create mode 100644 doc/examples/json_pointer__to_string.cpp
 create mode 100644 doc/examples/json_pointer__to_string.link
 create mode 100644 doc/examples/json_pointer__to_string.output

diff --git a/ChangeLog.md b/ChangeLog.md
index 0acd10f5..75827603 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -5,10 +5,12 @@ All notable changes to this project will be documented in this file. This projec
 
 [Full Changelog](https://github.com/nlohmann/json/compare/v1.1.0...HEAD)
 
+- Additional integration options [\#237](https://github.com/nlohmann/json/issues/237)
+- Can't use basic\_json::iterator as a base iterator for std::move\_iterator [\#233](https://github.com/nlohmann/json/issues/233)
 - Provide a FAQ [\#163](https://github.com/nlohmann/json/issues/163)
 - Create PULL\_REQUEST\_TEMPLATE.md [\#213](https://github.com/nlohmann/json/pull/213) ([whackashoe](https://github.com/whackashoe))
 - fixed noexcept; added constexpr [\#208](https://github.com/nlohmann/json/pull/208) ([nlohmann](https://github.com/nlohmann))
-- Add support for afl-fuzz testing [\#207](https://github.com/nlohmann/json/pull/207) ([msm-](https://github.com/msm-))
+- Add support for afl-fuzz testing [\#207](https://github.com/nlohmann/json/pull/207) ([mykter](https://github.com/mykter))
 - Issue \#178 - Extending support to full uint64\_t/int64\_t range and unsigned type \(updated\) [\#193](https://github.com/nlohmann/json/pull/193) ([twelsby](https://github.com/twelsby))
 
 - double values are serialized with commas as decimal points [\#228](https://github.com/nlohmann/json/issues/228)
@@ -26,6 +28,7 @@ All notable changes to this project will be documented in this file. This projec
 - Conflicting typedef of ssize\_t on Windows 32 bit when using Boost.Python [\#204](https://github.com/nlohmann/json/issues/204)
 - Integer conversion to unsigned [\#178](https://github.com/nlohmann/json/issues/178)
 
+- Implement additional integration options [\#238](https://github.com/nlohmann/json/pull/238) ([robertmrk](https://github.com/robertmrk))
 - make serialization locale-independent [\#232](https://github.com/nlohmann/json/pull/232) ([nlohmann](https://github.com/nlohmann))
 - fixes \#223 by updating README.md [\#227](https://github.com/nlohmann/json/pull/227) ([kevin--](https://github.com/kevin--))
 - Use namespace std for int64\_t and uint64\_t [\#226](https://github.com/nlohmann/json/pull/226) ([lv-zheng](https://github.com/lv-zheng))
diff --git a/doc/examples/json_pointer__to_string.cpp b/doc/examples/json_pointer__to_string.cpp
new file mode 100644
index 00000000..4cb053c5
--- /dev/null
+++ b/doc/examples/json_pointer__to_string.cpp
@@ -0,0 +1,34 @@
+#include <json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // different JSON Pointers
+    json::json_pointer ptr1("");
+    json::json_pointer ptr2("/foo");
+    json::json_pointer ptr3("/foo/0");
+    json::json_pointer ptr4("/");
+    json::json_pointer ptr5("/a~1b");
+    json::json_pointer ptr6("/c%d");
+    json::json_pointer ptr7("/e^f");
+    json::json_pointer ptr8("/g|h");
+    json::json_pointer ptr9("/i\\j");
+    json::json_pointer ptr10("/k\"l");
+    json::json_pointer ptr11("/ ");
+    json::json_pointer ptr12("/m~0n");
+
+
+    std::cout << ptr1.to_string() << '\n'
+              << ptr2.to_string() << '\n'
+              << ptr3.to_string() << '\n'
+              << ptr4.to_string() << '\n'
+              << ptr5.to_string() << '\n'
+              << ptr6.to_string() << '\n'
+              << ptr7.to_string() << '\n'
+              << ptr8.to_string() << '\n'
+              << ptr9.to_string() << '\n'
+              << ptr10.to_string() << '\n'
+              << ptr11.to_string() << '\n'
+              << ptr12.to_string() << std::endl;
+}
diff --git a/doc/examples/json_pointer__to_string.link b/doc/examples/json_pointer__to_string.link
new file mode 100644
index 00000000..407609b2
--- /dev/null
+++ b/doc/examples/json_pointer__to_string.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/O4FbKx0TbZioFhfU"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/json_pointer__to_string.output b/doc/examples/json_pointer__to_string.output
new file mode 100644
index 00000000..c4b5ea8f
--- /dev/null
+++ b/doc/examples/json_pointer__to_string.output
@@ -0,0 +1,12 @@
+
+/foo
+/foo/0
+/
+/a~1b
+/c%d
+/e^f
+/g|h
+/i\j
+/k"l
+/ 
+/m~0n
diff --git a/src/json.hpp b/src/json.hpp
index 1785f182..6b22e89d 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8818,6 +8818,10 @@ basic_json_parser_63:
     /*!
     @brief JSON Pointer
 
+    A JSON pointer defines a string syntax for identifying a specific value
+    within a JSON document. It can be used with functions `at` and
+    `operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
     @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
 
     @since version 2.0.0
@@ -8854,10 +8858,37 @@ basic_json_parser_63:
             : reference_tokens(split(s))
         {}
 
-        /// test for inequality
-        bool operator!=(const json_pointer& rhs) const
+        /*!
+        @brief return a string representation of the JSON pointer
+
+        @invariant For each JSON pointer `ptr`, it holds:
+        @code {.cpp}
+        ptr == json_pointer(ptr.to_string());
+        @endcode
+
+        @return a string representation of the JSON pointer
+
+        @liveexample{The example shows the result of `to_string`.,
+        json_pointer__to_string}
+
+        @since version 2.0.0
+        */
+        std::string to_string() const noexcept
         {
-            return reference_tokens != rhs.reference_tokens;
+            std::string result;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                result += "/" + escape(reference_token);
+            }
+
+            return result;
+        }
+
+        /// @copydoc to_string()
+        operator std::string() const
+        {
+            return to_string();
         }
 
       private:
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 51b72167..3dab33bb 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -8128,6 +8128,10 @@ class basic_json
     /*!
     @brief JSON Pointer
 
+    A JSON pointer defines a string syntax for identifying a specific value
+    within a JSON document. It can be used with functions `at` and
+    `operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
     @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
 
     @since version 2.0.0
@@ -8164,10 +8168,37 @@ class basic_json
             : reference_tokens(split(s))
         {}
 
-        /// test for inequality
-        bool operator!=(const json_pointer& rhs) const
+        /*!
+        @brief return a string representation of the JSON pointer
+
+        @invariant For each JSON pointer `ptr`, it holds:
+        @code {.cpp}
+        ptr == json_pointer(ptr.to_string());
+        @endcode
+
+        @return a string representation of the JSON pointer
+
+        @liveexample{The example shows the result of `to_string`.,
+        json_pointer__to_string}
+
+        @since version 2.0.0
+        */
+        std::string to_string() const noexcept
         {
-            return reference_tokens != rhs.reference_tokens;
+            std::string result;
+
+            for (const auto& reference_token : reference_tokens)
+            {
+                result += "/" + escape(reference_token);
+            }
+
+            return result;
+        }
+
+        /// @copydoc to_string()
+        operator std::string() const
+        {
+            return to_string();
         }
 
       private:
diff --git a/test/unit.cpp b/test/unit.cpp
index f6c49883..d038b867 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12035,6 +12035,36 @@ TEST_CASE("Unicode", "[hide]")
 
         // the array has 1112064 + 1 elemnts (a terminating "null" value)
         CHECK(j.size() == 1112065);
+
+        SECTION("check JSON Pointers")
+        {
+            for (auto s : j)
+            {
+                // skip non-string JSON values
+                if (not s.is_string())
+                {
+                    continue;
+                }
+
+                std::string ptr = s;
+
+                // tilde must be followed by 0 or 1
+                if (ptr == "~")
+                {
+                    ptr += "0";
+                }
+
+                // JSON Pointers must begin with "/"
+                ptr = "/" + ptr;
+
+                CHECK_NOTHROW(json::json_pointer("/" + ptr));
+
+                // check escape/unescape roundtrip
+                auto escaped = json::json_pointer::escape(ptr);
+                json::json_pointer::unescape(escaped);
+                CHECK(escaped == ptr);
+            }
+        }
     }
 
     SECTION("ignore byte-order-mark")
@@ -12389,6 +12419,17 @@ TEST_CASE("JSON pointers")
         json j_object(json::value_t::object);
         CHECK(j_object.flatten().unflatten() == json());
     }
+
+    SECTION("string representation")
+    {
+        for (auto ptr :
+                {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n"
+                })
+        {
+            CHECK(json::json_pointer(ptr).to_string() == ptr);
+            CHECK(json::json_pointer(ptr) == ptr);
+        }
+    }
 }
 
 TEST_CASE("JSON patch")

From f4ecceab19442e46813de74c3f1571e16ceffa42 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 00:12:19 +0200
Subject: [PATCH 11/19] cleanup

---
 test/unit.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/unit.cpp b/test/unit.cpp
index d038b867..2ad28a74 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12427,7 +12427,6 @@ TEST_CASE("JSON pointers")
                 })
         {
             CHECK(json::json_pointer(ptr).to_string() == ptr);
-            CHECK(json::json_pointer(ptr) == ptr);
         }
     }
 }

From be16d005e4e7167c23344e78b72b5229706d347d Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 00:25:11 +0200
Subject: [PATCH 12/19] get compiler versions

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index 882f98d2..979c5aab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -109,6 +109,7 @@ matrix:
       env: COMPILER=clang
 
 script:
+  - $COMPILER --version
   - make CXX=$COMPILER CXXFLAGS="-lstdc++"
   - ./json_unit "*"
   - if [ `which valgrind` ]; then

From 7c04bc3db659cfd02bbe1903bea4447a672aed83 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 00:43:33 +0200
Subject: [PATCH 13/19] added name call

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index 979c5aab..d1748dff 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -109,6 +109,7 @@ matrix:
       env: COMPILER=clang
 
 script:
+  - uname -a
   - $COMPILER --version
   - make CXX=$COMPILER CXXFLAGS="-lstdc++"
   - ./json_unit "*"

From 9170740f45b033c9fbd06f084890e18bffec091e Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 01:01:41 +0200
Subject: [PATCH 14/19] added documentation on Travis compilers

---
 README.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/README.md b/README.md
index 90be576c..1f1e0927 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,8 @@ Though it's 2016 already, the support for C++11 is still a bit sparse. Currently
 - Clang 3.4 - 3.9 (and possibly later)
 - Microsoft Visual C++ 14.0 RC (and possibly later)
 
+The code is currently checked with Travis for GCC 4.9, GCC 
+
 I would be happy to learn about other compilers/versions.
 
 Please note:
@@ -63,6 +65,24 @@ Please note:
  
 - For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code,  but rather with the compiler itself. On Android, see above to build with a newer environment.  For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug.
 
+The following compilers are currently used in [continuous integration](https://travis-ci.org/nlohmann/json):
+
+| Compiler        | Operating System             | Version String |
+|-----------------|------------------------------|----------------|
+| GCC 4.9.3       | Ubuntu 14.04.3 LTS           | g++-4.9 (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3 |
+| GCC 5.3.0       | Ubuntu 14.04.3 LTS           | g++-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204 |
+| Clang 3.6.2     | Ubuntu 14.04.3 LTS           | Ubuntu clang version 3.6.2-svn240577-1~exp1 (branches/release_36) (based on LLVM 3.6.2) |
+| Clang 3.7.1     | Ubuntu 14.04.3 LTS           | Ubuntu clang version 3.7.1-svn253571-1~exp1 (branches/release_37) (based on LLVM 3.7.1) |
+| Clang 3.8.1     | Ubuntu 14.04.3 LTS           | clang version 3.8.1-svn265380-1~exp1 (branches/release_38) |
+| Clang 3.9.0     | Ubuntu 14.04.3 LTS           | clang version 3.9.0-svn267478-1~exp1 (trunk) |
+| Clang Xcode 6.1 | Darwin Kernel Version 13.4.0 | Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) |
+| Clang Xcode 6.2 | Darwin Kernel Version 13.4.0 | Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) |
+| Clang Xcode 6.3 | Darwin Kernel Version 14.3.0 | Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn) |
+| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
+| Clang Xcode 7.1 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.0.0 (clang-700.1.76) |
+| Clang Xcode 7.2 | Darwin Kernel Version 15.0.0 | Apple LLVM version 7.0.2 (clang-700.1.81) |
+| Clang Xcode 7.3 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.3.0 (clang-703.0.29) |
+
 ## Examples
 
 Here are some examples to give you an idea how to use the class.

From a21f8b0c77646f3eb225bc98ed3f9ca586101068 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 01:21:35 +0200
Subject: [PATCH 15/19] added AppVeyor information

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 1f1e0927..28794d9c 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ The following compilers are currently used in [continuous integration](https://t
 | Clang Xcode 7.1 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.0.0 (clang-700.1.76) |
 | Clang Xcode 7.2 | Darwin Kernel Version 15.0.0 | Apple LLVM version 7.0.2 (clang-700.1.81) |
 | Clang Xcode 7.3 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.3.0 (clang-703.0.29) |
+| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25123.0 | 
 
 ## Examples
 

From ea84a85b132b2e9bdf1e61aa55b657eba5e254f1 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 30 Apr 2016 10:39:03 +0200
Subject: [PATCH 16/19] simplified flatten/unflatten examples

---
 README.md                     | 2 +-
 doc/examples/flatten.cpp      | 9 +--------
 doc/examples/flatten.link     | 2 +-
 doc/examples/flatten.output   | 5 -----
 doc/examples/unflatten.cpp    | 4 ----
 doc/examples/unflatten.link   | 2 +-
 doc/examples/unflatten.output | 6 +-----
 7 files changed, 5 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index 28794d9c..1f501b73 100644
--- a/README.md
+++ b/README.md
@@ -449,7 +449,7 @@ $ make
 $ ./json_unit "*"
 
 ===============================================================================
-All tests passed (3344554 assertions in 31 test cases)
+All tests passed (5568699 assertions in 31 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 0601f8a3..ace53a21 100644
--- a/doc/examples/flatten.cpp
+++ b/doc/examples/flatten.cpp
@@ -20,18 +20,11 @@ int main()
         {
             "object", {
                 {"currency", "USD"},
-                {"value", 42.99},
-                {"", "empty string"},
-                {"/", "slash"},
-                {"~", "tilde"},
-                {"~1", "tilde1"}
+                {"value", 42.99}
             }
         }
     };
 
     // 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 0fe78bbb..817d0627 100644
--- a/doc/examples/flatten.link
+++ b/doc/examples/flatten.link
@@ -1 +1 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/skGi8b32VhI8HOgV"><b>online</b></a>
\ No newline at end of file
+<a target="_blank" href="http://melpon.org/wandbox/permlink/OZG9sHcJCXsScNiy"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/flatten.output b/doc/examples/flatten.output
index fedfc8ef..33bd4c4b 100644
--- a/doc/examples/flatten.output
+++ b/doc/examples/flatten.output
@@ -6,12 +6,7 @@
     "/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
 }
-{"":3.141}
diff --git a/doc/examples/unflatten.cpp b/doc/examples/unflatten.cpp
index 39c674c9..e2b9b6b8 100644
--- a/doc/examples/unflatten.cpp
+++ b/doc/examples/unflatten.cpp
@@ -14,12 +14,8 @@ int main()
         {"/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}
     };
 
diff --git a/doc/examples/unflatten.link b/doc/examples/unflatten.link
index bc7594a0..d564ab01 100644
--- a/doc/examples/unflatten.link
+++ b/doc/examples/unflatten.link
@@ -1 +1 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/ITqCZsXmi0I7KGYy"><b>online</b></a>
\ No newline at end of file
+<a target="_blank" href="http://melpon.org/wandbox/permlink/m2UmItixDxnYs1Se"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/unflatten.output b/doc/examples/unflatten.output
index f57c9c9a..ed48385a 100644
--- a/doc/examples/unflatten.output
+++ b/doc/examples/unflatten.output
@@ -11,12 +11,8 @@
     "name": "Niels",
     "nothing": null,
     "object": {
-        "": "empty string",
-        "/": "slash",
         "currency": "USD",
-        "value": 42.99,
-        "~": "tilde",
-        "~1": "tilde1"
+        "value": 42.99
     },
     "pi": 3.141
 }

From c04275966f8e1d2aba154074ef823b5973e1cb68 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 7 May 2016 18:33:43 +0200
Subject: [PATCH 17/19] improved test coverage

---
 README.md         | 2 +-
 src/json.hpp      | 3 ++-
 src/json.hpp.re2c | 3 ++-
 test/unit.cpp     | 6 ++++++
 4 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 1f501b73..f8cb20d6 100644
--- a/README.md
+++ b/README.md
@@ -449,7 +449,7 @@ $ make
 $ ./json_unit "*"
 
 ===============================================================================
-All tests passed (5568699 assertions in 31 test cases)
+All tests passed (5568703 assertions in 31 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/src/json.hpp b/src/json.hpp
index 6b22e89d..932ab9e5 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -9708,7 +9708,8 @@ basic_json_parser_63:
 
                     default:
                     {
-                        throw std::domain_error("unexpected parent type " + parent.type_name());
+                        // if there exists a parent it cannot be primitive
+                        assert(false);  // LCOV_EXCL_LINE
                     }
                 }
             }
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 3dab33bb..77a1eea9 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -9018,7 +9018,8 @@ class basic_json
 
                     default:
                     {
-                        throw std::domain_error("unexpected parent type " + parent.type_name());
+                        // if there exists a parent it cannot be primitive
+                        assert(false);  // LCOV_EXCL_LINE
                     }
                 }
             }
diff --git a/test/unit.cpp b/test/unit.cpp
index 2ad28a74..de00166f 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12094,6 +12094,12 @@ TEST_CASE("JSON pointers")
 
         CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
         CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
+
+        json::json_pointer p;
+        CHECK_THROWS_AS(p.top(), std::domain_error);
+        CHECK_THROWS_WITH(p.top(), "JSON pointer has no parent");
+        CHECK_THROWS_AS(p.pop_back(), std::domain_error);
+        CHECK_THROWS_WITH(p.pop_back(), "JSON pointer has no parent");
     }
 
     SECTION("examples from RFC 6901")

From 85a30813c8d9b0eccefcdc59e9e4e602a7fffae7 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sat, 7 May 2016 20:22:58 +0200
Subject: [PATCH 18/19] removed unnecessary code (numbers must not begin with
 "+")

---
 README.md         | 2 +-
 src/json.hpp      | 4 ----
 src/json.hpp.re2c | 4 ----
 test/unit.cpp     | 4 ++++
 4 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index f8cb20d6..c9f3a713 100644
--- a/README.md
+++ b/README.md
@@ -449,7 +449,7 @@ $ make
 $ ./json_unit "*"
 
 ===============================================================================
-All tests passed (5568703 assertions in 31 test cases)
+All tests passed (5568705 assertions in 31 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/src/json.hpp b/src/json.hpp
index 932ab9e5..51536550 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -8448,10 +8448,6 @@ basic_json_parser_63:
             {
                 type = value_t::number_unsigned;
                 max = static_cast<uint64_t>(std::numeric_limits<number_unsigned_t>::max());
-                if (*curptr == '+')
-                {
-                    curptr++;
-                }
             }
 
             // count the significant figures
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index 77a1eea9..c8b66e92 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -7758,10 +7758,6 @@ class basic_json
             {
                 type = value_t::number_unsigned;
                 max = static_cast<uint64_t>(std::numeric_limits<number_unsigned_t>::max());
-                if (*curptr == '+')
-                {
-                    curptr++;
-                }
             }
 
             // count the significant figures
diff --git a/test/unit.cpp b/test/unit.cpp
index de00166f..9b735108 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -9776,6 +9776,10 @@ TEST_CASE("parser class")
                 CHECK_THROWS_AS(json::parser("-0e-:").parse(), std::invalid_argument);
                 CHECK_THROWS_AS(json::parser("-0f").parse(), std::invalid_argument);
 
+                // numbers must not begin with "+"
+                CHECK_THROWS_AS(json::parser("+1").parse(), std::invalid_argument);
+                CHECK_THROWS_AS(json::parser("+0").parse(), std::invalid_argument);
+
                 CHECK_THROWS_WITH(json::parser("01").parse(),
                                   "parse error - unexpected number literal; expected end of input");
                 CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'");

From fadf286653c41823804bd224d88db39d7835c9da Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 8 May 2016 14:35:32 +0200
Subject: [PATCH 19/19] added test case for std::bad_alloc

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

diff --git a/test/unit.cpp b/test/unit.cpp
index 9b735108..af52e175 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -13993,3 +13993,34 @@ TEST_CASE("regression tests")
         CHECK(dest == expected);
     }
 }
+
+// special test case to check if memory is leaked if constructor throws
+
+template<class T>
+struct my_allocator : std::allocator<T>
+{
+    template<class... Args>
+    void construct(T*, Args&& ...)
+    {
+        throw std::bad_alloc();
+    }
+};
+
+TEST_CASE("bad_alloc")
+{
+    SECTION("bad_alloc")
+    {
+        // create JSON type using the throwing allocator
+        using my_json = nlohmann::basic_json<std::map,
+              std::vector,
+              std::string,
+              bool,
+              std::int64_t,
+              std::uint64_t,
+              double,
+              my_allocator>;
+
+        // creating an object should throw
+        CHECK_THROWS_AS(my_json j(my_json::value_t::object), std::bad_alloc);
+    }
+}