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