From 258fa798f15a0010d04eaa23010be8b53afd2f4d Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
Date: Sun, 30 Jun 2019 10:03:08 +0200
Subject: [PATCH] :sparkles: add contains function for JSON pointers

---
 doc/examples/contains_json_pointer.cpp    |  45 ++++++++
 doc/examples/contains_json_pointer.link   |   1 +
 doc/examples/contains_json_pointer.output |  10 ++
 include/nlohmann/detail/json_pointer.hpp  |  90 +++++++++++++--
 include/nlohmann/json.hpp                 |  37 ++++++-
 single_include/nlohmann/json.hpp          | 127 ++++++++++++++++++++--
 test/src/unit-json_pointer.cpp            |  51 ++++++++-
 7 files changed, 338 insertions(+), 23 deletions(-)
 create mode 100644 doc/examples/contains_json_pointer.cpp
 create mode 100644 doc/examples/contains_json_pointer.link
 create mode 100644 doc/examples/contains_json_pointer.output

diff --git a/doc/examples/contains_json_pointer.cpp b/doc/examples/contains_json_pointer.cpp
new file mode 100644
index 00000000..ed45fcee
--- /dev/null
+++ b/doc/examples/contains_json_pointer.cpp
@@ -0,0 +1,45 @@
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // create a JSON value
+    json j =
+    {
+        {"number", 1}, {"string", "foo"}, {"array", {1, 2}}
+    };
+
+    std::cout << std::boolalpha
+              << j.contains("/number"_json_pointer) << '\n'
+              << j.contains("/string"_json_pointer) << '\n'
+              << j.contains("/string"_json_pointer) << '\n'
+              << j.contains("/array"_json_pointer) << '\n'
+              << j.contains("/array/1"_json_pointer) << '\n'
+              << j.contains("/array/-"_json_pointer) << '\n'
+              << j.contains("/array/4"_json_pointer) << '\n'
+              << j.contains("/baz"_json_pointer) << std::endl;
+
+    // out_of_range.106
+    try
+    {
+        // try to use an array index with leading '0'
+        j.contains("/array/01"_json_pointer);
+    }
+    catch (json::parse_error& e)
+    {
+        std::cout << e.what() << '\n';
+    }
+
+    // out_of_range.109
+    try
+    {
+        // try to use an array index that is not a number
+        j.contains("/array/one"_json_pointer);
+    }
+    catch (json::parse_error& e)
+    {
+        std::cout << e.what() << '\n';
+    }
+}
diff --git a/doc/examples/contains_json_pointer.link b/doc/examples/contains_json_pointer.link
new file mode 100644
index 00000000..778e151f
--- /dev/null
+++ b/doc/examples/contains_json_pointer.link
@@ -0,0 +1 @@
+<a target="_blank" href="https://wandbox.org/permlink/3TJ79OzHP4vmN1Nb"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/contains_json_pointer.output b/doc/examples/contains_json_pointer.output
new file mode 100644
index 00000000..82ec3675
--- /dev/null
+++ b/doc/examples/contains_json_pointer.output
@@ -0,0 +1,10 @@
+true
+true
+true
+true
+true
+false
+false
+false
+[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'
+[json.exception.parse_error.109] parse error: array index 'one' is not a number
diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp
index 465e5165..51c51efa 100644
--- a/include/nlohmann/detail/json_pointer.hpp
+++ b/include/nlohmann/detail/json_pointer.hpp
@@ -2,6 +2,7 @@
 
 #include <algorithm> // all_of
 #include <cassert> // assert
+#include <cctype> // isdigit
 #include <numeric> // accumulate
 #include <string> // string
 #include <utility> // move
@@ -369,7 +370,7 @@ class json_pointer
         // j which will be overwritten by a primitive value
         for (const auto& reference_token : reference_tokens)
         {
-            switch (result->m_type)
+            switch (result->type())
             {
                 case detail::value_t::null:
                 {
@@ -446,14 +447,14 @@ class json_pointer
         for (const auto& reference_token : reference_tokens)
         {
             // convert null values to arrays or objects before continuing
-            if (ptr->m_type == detail::value_t::null)
+            if (ptr->is_null())
             {
                 // check if reference token is a number
                 const bool nums =
                     std::all_of(reference_token.begin(), reference_token.end(),
-                                [](const char x)
+                                [](const unsigned char x)
                 {
-                    return x >= '0' and x <= '9';
+                    return std::isdigit(x);
                 });
 
                 // change value to array for numbers or "-" or to object otherwise
@@ -462,7 +463,7 @@ class json_pointer
                        : detail::value_t::object;
             }
 
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -521,7 +522,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -586,7 +587,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -645,7 +646,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -692,6 +693,77 @@ class json_pointer
         return *ptr;
     }
 
+    /*!
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    */
+    bool contains(const BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (ptr->type())
+            {
+                case detail::value_t::object:
+                {
+                    if (not ptr->contains(reference_token))
+                    {
+                        // we did not find the key in the object
+                        return false;
+                    }
+
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    if (JSON_UNLIKELY(reference_token == "-"))
+                    {
+                        // "-" always fails the range check
+                        return false;
+                    }
+
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    JSON_TRY
+                    {
+                        const auto idx = static_cast<size_type>(array_index(reference_token));
+                        if (idx >= ptr->size())
+                        {
+                            // index out of range
+                            return false;
+                        }
+
+                        ptr = &ptr->operator[](idx);
+                        break;
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // we do not expect primitive values if there is still a
+                    // reference token to process
+                    return false;
+                }
+            }
+        }
+
+        // no reference token left means we found a primitive value
+        return true;
+    }
+
     /*!
     @brief split the string input to reference tokens
 
@@ -813,7 +885,7 @@ class json_pointer
                         const BasicJsonType& value,
                         BasicJsonType& result)
     {
-        switch (value.m_type)
+        switch (value.type())
         {
             case detail::value_t::array:
             {
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 58db19d7..bef79c2c 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -4001,15 +4001,48 @@ class basic_json
     @liveexample{The following code shows an example for `contains()`.,contains}
 
     @sa @ref find(KeyT&&) -- returns an iterator to an object element
+    @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer
 
     @since version 3.6.0
     */
-    template<typename KeyT>
-    bool contains(KeyT&& key) const
+    template<typename KeyT, typename std::enable_if<
+                 not std::is_same<KeyT, json_pointer>::value, int>::type = 0>
+    bool contains(KeyT && key) const
     {
         return is_object() and m_value.object->find(std::forward<KeyT>(key)) != m_value.object->end();
     }
 
+    /*!
+    @brief check the existence of an element in a JSON object given a JSON pointer
+
+    Check wehther the given JSON pointer @a ptr can be resolved in the current
+    JSON value.
+
+    @note This method can be executed on any JSON value type.
+
+    @param[in] ptr JSON pointer to check its existence.
+
+    @return true if the JSON pointer can be resolved to a stored value, false
+    otherwise.
+
+    @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`.
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+
+    @complexity Logarithmic in the size of the JSON object.
+
+    @liveexample{The following code shows an example for `contains()`.,contains_json_pointer}
+
+    @sa @ref contains(KeyT &&) const -- checks the existence of a key
+
+    @since version 3.7.0
+    */
+    bool contains(const json_pointer& ptr) const
+    {
+        return ptr.contains(this);
+    }
+
     /// @}
 
 
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index bb2dacbc..872a9b69 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -8445,6 +8445,7 @@ class json_reverse_iterator : public std::reverse_iterator<Base>
 
 #include <algorithm> // all_of
 #include <cassert> // assert
+#include <cctype> // isdigit
 #include <numeric> // accumulate
 #include <string> // string
 #include <utility> // move
@@ -8815,7 +8816,7 @@ class json_pointer
         // j which will be overwritten by a primitive value
         for (const auto& reference_token : reference_tokens)
         {
-            switch (result->m_type)
+            switch (result->type())
             {
                 case detail::value_t::null:
                 {
@@ -8892,14 +8893,14 @@ class json_pointer
         for (const auto& reference_token : reference_tokens)
         {
             // convert null values to arrays or objects before continuing
-            if (ptr->m_type == detail::value_t::null)
+            if (ptr->is_null())
             {
                 // check if reference token is a number
                 const bool nums =
                     std::all_of(reference_token.begin(), reference_token.end(),
-                                [](const char x)
+                                [](const unsigned char x)
                 {
-                    return x >= '0' and x <= '9';
+                    return std::isdigit(x);
                 });
 
                 // change value to array for numbers or "-" or to object otherwise
@@ -8908,7 +8909,7 @@ class json_pointer
                        : detail::value_t::object;
             }
 
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -8967,7 +8968,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -9032,7 +9033,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -9091,7 +9092,7 @@ class json_pointer
         using size_type = typename BasicJsonType::size_type;
         for (const auto& reference_token : reference_tokens)
         {
-            switch (ptr->m_type)
+            switch (ptr->type())
             {
                 case detail::value_t::object:
                 {
@@ -9138,6 +9139,77 @@ class json_pointer
         return *ptr;
     }
 
+    /*!
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    */
+    bool contains(const BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (ptr->type())
+            {
+                case detail::value_t::object:
+                {
+                    if (not ptr->contains(reference_token))
+                    {
+                        // we did not find the key in the object
+                        return false;
+                    }
+
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    if (JSON_UNLIKELY(reference_token == "-"))
+                    {
+                        // "-" always fails the range check
+                        return false;
+                    }
+
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    JSON_TRY
+                    {
+                        const auto idx = static_cast<size_type>(array_index(reference_token));
+                        if (idx >= ptr->size())
+                        {
+                            // index out of range
+                            return false;
+                        }
+
+                        ptr = &ptr->operator[](idx);
+                        break;
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    // we do not expect primitive values if there is still a
+                    // reference token to process
+                    return false;
+                }
+            }
+        }
+
+        // no reference token left means we found a primitive value
+        return true;
+    }
+
     /*!
     @brief split the string input to reference tokens
 
@@ -9259,7 +9331,7 @@ class json_pointer
                         const BasicJsonType& value,
                         BasicJsonType& result)
     {
-        switch (value.m_type)
+        switch (value.type())
         {
             case detail::value_t::array:
             {
@@ -16802,15 +16874,48 @@ class basic_json
     @liveexample{The following code shows an example for `contains()`.,contains}
 
     @sa @ref find(KeyT&&) -- returns an iterator to an object element
+    @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer
 
     @since version 3.6.0
     */
-    template<typename KeyT>
-    bool contains(KeyT&& key) const
+    template<typename KeyT, typename std::enable_if<
+                 not std::is_same<KeyT, json_pointer>::value, int>::type = 0>
+    bool contains(KeyT && key) const
     {
         return is_object() and m_value.object->find(std::forward<KeyT>(key)) != m_value.object->end();
     }
 
+    /*!
+    @brief check the existence of an element in a JSON object given a JSON pointer
+
+    Check wehther the given JSON pointer @a ptr can be resolved in the current
+    JSON value.
+
+    @note This method can be executed on any JSON value type.
+
+    @param[in] ptr JSON pointer to check its existence.
+
+    @return true if the JSON pointer can be resolved to a stored value, false
+    otherwise.
+
+    @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`.
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+
+    @complexity Logarithmic in the size of the JSON object.
+
+    @liveexample{The following code shows an example for `contains()`.,contains_json_pointer}
+
+    @sa @ref contains(KeyT &&) const -- checks the existence of a key
+
+    @since version 3.7.0
+    */
+    bool contains(const json_pointer& ptr) const
+    {
+        return ptr.contains(this);
+    }
+
     /// @}
 
 
diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp
index 6e6a1d79..598f36b0 100644
--- a/test/src/unit-json_pointer.cpp
+++ b/test/src/unit-json_pointer.cpp
@@ -90,12 +90,18 @@ TEST_CASE("JSON pointers")
             // the whole document
             CHECK(j[json::json_pointer()] == j);
             CHECK(j[json::json_pointer("")] == j);
+            CHECK(j.contains(json::json_pointer()));
+            CHECK(j.contains(json::json_pointer("")));
 
             // array access
             CHECK(j[json::json_pointer("/foo")] == j["foo"]);
+            CHECK(j.contains(json::json_pointer("/foo")));
             CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
+            CHECK(j.contains(json::json_pointer("/foo/0")));
+            CHECK(j.contains(json::json_pointer("/foo/1")));
+            CHECK(not j.contains(json::json_pointer("/foo/-")));
 
             // checked array access
             CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
@@ -103,6 +109,8 @@ TEST_CASE("JSON pointers")
 
             // empty string access
             CHECK(j[json::json_pointer("/")] == j[""]);
+            CHECK(j.contains(json::json_pointer("")));
+            CHECK(j.contains(json::json_pointer("/")));
 
             // other cases
             CHECK(j[json::json_pointer("/ ")] == j[" "]);
@@ -112,6 +120,14 @@ TEST_CASE("JSON pointers")
             CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
             CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
 
+            // contains
+            CHECK(j.contains(json::json_pointer("/ ")));
+            CHECK(j.contains(json::json_pointer("/c%d")));
+            CHECK(j.contains(json::json_pointer("/e^f")));
+            CHECK(j.contains(json::json_pointer("/g|h")));
+            CHECK(j.contains(json::json_pointer("/i\\j")));
+            CHECK(j.contains(json::json_pointer("/k\"l")));
+
             // checked access
             CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
             CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
@@ -123,14 +139,24 @@ TEST_CASE("JSON pointers")
             // escaped access
             CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
             CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
+            CHECK(j.contains(json::json_pointer("/a~1b")));
+            CHECK(j.contains(json::json_pointer("/m~0n")));
 
             // unescaped access
             // access to nonexisting values yield object creation
+            CHECK(not j.contains(json::json_pointer("/a/b")));
             CHECK_NOTHROW(j[json::json_pointer("/a/b")] = 42);
+            CHECK(j.contains(json::json_pointer("/a/b")));
             CHECK(j["a"]["b"] == json(42));
+
+            CHECK(not j.contains(json::json_pointer("/a/c/1")));
             CHECK_NOTHROW(j[json::json_pointer("/a/c/1")] = 42);
             CHECK(j["a"]["c"] == json({nullptr, 42}));
+            CHECK(j.contains(json::json_pointer("/a/c/1")));
+
+            CHECK(not j.contains(json::json_pointer("/a/d/-")));
             CHECK_NOTHROW(j[json::json_pointer("/a/d/-")] = 42);
+            CHECK(not j.contains(json::json_pointer("/a/d/-")));
             CHECK(j["a"]["d"] == json::array({42}));
             // "/a/b" works for JSON {"a": {"b": 42}}
             CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
@@ -143,6 +169,7 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), json::out_of_range&);
             CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer),
                               "[json.exception.out_of_range.404] unresolved reference token 'foo'");
+            CHECK(not j_primitive.contains(json::json_pointer("/foo")));
         }
 
         SECTION("const access")
@@ -233,11 +260,16 @@ TEST_CASE("JSON pointers")
 
             // the whole document
             CHECK(j[""_json_pointer] == j);
+            CHECK(j.contains(""_json_pointer));
 
             // array access
             CHECK(j["/foo"_json_pointer] == j["foo"]);
             CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
+            CHECK(j.contains("/foo"_json_pointer));
+            CHECK(j.contains("/foo/0"_json_pointer));
+            CHECK(j.contains("/foo/1"_json_pointer));
+            CHECK(not j.contains("/foo/-"_json_pointer));
         }
     }
 
@@ -278,6 +310,12 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j_const.at("/01"_json_pointer), json::parse_error&);
             CHECK_THROWS_WITH(j_const.at("/01"_json_pointer),
                               "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+            CHECK_THROWS_AS(j.contains("/01"_json_pointer), json::parse_error&);
+            CHECK_THROWS_WITH(j.contains("/01"_json_pointer),
+                              "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
+            CHECK_THROWS_AS(j_const.contains("/01"_json_pointer), json::parse_error&);
+            CHECK_THROWS_WITH(j_const.contains("/01"_json_pointer),
+                              "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'");
 
             // error with incorrect numbers
             CHECK_THROWS_AS(j["/one"_json_pointer] = 1, json::parse_error&);
@@ -294,6 +332,13 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_WITH(j_const.at("/one"_json_pointer) == 1,
                               "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
 
+            CHECK_THROWS_AS(j.contains("/one"_json_pointer), json::parse_error&);
+            CHECK_THROWS_WITH(j.contains("/one"_json_pointer),
+                              "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+            CHECK_THROWS_AS(j_const.contains("/one"_json_pointer), json::parse_error&);
+            CHECK_THROWS_WITH(j_const.contains("/one"_json_pointer),
+                              "[json.exception.parse_error.109] parse error: array index 'one' is not a number");
+
             CHECK_THROWS_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(), json::parse_error&);
             CHECK_THROWS_WITH(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(),
             "[json.exception.parse_error.109] parse error: array index 'three' is not a number");
@@ -306,6 +351,7 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j_const["/-"_json_pointer], json::out_of_range&);
             CHECK_THROWS_WITH(j_const["/-"_json_pointer],
                               "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+            CHECK(not j_const.contains("/-"_json_pointer));
 
             // error when using "-" with at
             CHECK_THROWS_AS(j.at("/-"_json_pointer), json::out_of_range&);
@@ -314,6 +360,7 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j_const.at("/-"_json_pointer), json::out_of_range&);
             CHECK_THROWS_WITH(j_const.at("/-"_json_pointer),
                               "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+            CHECK(not j_const.contains("/-"_json_pointer));
         }
 
         SECTION("const access")
@@ -329,11 +376,13 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j.at("/3"_json_pointer), json::out_of_range&);
             CHECK_THROWS_WITH(j.at("/3"_json_pointer),
                               "[json.exception.out_of_range.401] array index 3 is out of range");
+            CHECK(not j.contains("/3"_json_pointer));
 
             // assign to nonexisting index (with gap)
             CHECK_THROWS_AS(j.at("/5"_json_pointer), json::out_of_range&);
             CHECK_THROWS_WITH(j.at("/5"_json_pointer),
                               "[json.exception.out_of_range.401] array index 5 is out of range");
+            CHECK(not j.contains("/5"_json_pointer));
 
             // assign to "-"
             CHECK_THROWS_AS(j["/-"_json_pointer], json::out_of_range&);
@@ -342,8 +391,8 @@ TEST_CASE("JSON pointers")
             CHECK_THROWS_AS(j.at("/-"_json_pointer), json::out_of_range&);
             CHECK_THROWS_WITH(j.at("/-"_json_pointer),
                               "[json.exception.out_of_range.402] array index '-' (3) is out of range");
+            CHECK(not j.contains("/-"_json_pointer));
         }
-
     }
 
     SECTION("flatten")