diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 033625bc4c..28a7e8f317 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -76,6 +76,67 @@ class json_pointer return to_string(); } + /*! + @brief append another JSON pointer at the end of this JSON pointer + */ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); + return *this; + } + + /// @copydoc push_back(std::string&&) + json_pointer& operator/=(std::string token) + { + push_back(std::move(token)); + return *this; + } + + /// @copydoc operator/=(std::string) + json_pointer& operator/=(std::size_t array_index) + { + return *this /= std::to_string(array_index); + } + + /*! + @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + */ + friend json_pointer operator/(const json_pointer& left_ptr, const json_pointer& right_ptr) + { + return json_pointer(left_ptr) /= right_ptr; + } + + /*! + @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + */ + friend json_pointer operator/(const json_pointer& ptr, std::string token) + { + return json_pointer(ptr) /= std::move(token); + } + + /*! + @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + */ + friend json_pointer operator/(const json_pointer& lhs, std::size_t array_index) + { + return json_pointer(lhs) /= array_index; + } + + /*! + @brief create a new JSON pointer that is the parent of this JSON pointer + */ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + /*! @param[in] s reference token to be converted into an array index @@ -98,12 +159,12 @@ class json_pointer } /*! - @brief remove and return last reference pointer + @brief remove and return last reference token @throw out_of_range.405 if JSON pointer has no parent */ std::string pop_back() { - if (JSON_UNLIKELY(is_root())) + if (JSON_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } @@ -114,24 +175,29 @@ class json_pointer } /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent + @brief append an unescaped token at the end of the reference pointer */ - void push_back(const std::string& tok) + void push_back(const std::string& token) { - reference_tokens.push_back(tok); + reference_tokens.push_back(token); + } + + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) + { + reference_tokens.push_back(std::move(token)); } - private: /// return whether pointer points to the root document - bool is_root() const noexcept + bool empty() const noexcept { return reference_tokens.empty(); } + private: json_pointer top() const { - if (JSON_UNLIKELY(is_root())) + if (JSON_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index f0aff55e9a..50f7980f62 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7493,7 +7493,7 @@ class basic_json const auto operation_add = [&result](json_pointer & ptr, basic_json val) { // adding to the root of the target document means replacing it - if (ptr.is_root()) + if (ptr.empty()) { result = val; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2e06ef335f..ded6c2bcce 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11896,6 +11896,67 @@ class json_pointer return to_string(); } + /*! + @brief append another JSON pointer at the end of this JSON pointer + */ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); + return *this; + } + + /// @copydoc push_back(std::string&&) + json_pointer& operator/=(std::string token) + { + push_back(std::move(token)); + return *this; + } + + /// @copydoc operator/=(std::string) + json_pointer& operator/=(std::size_t array_index) + { + return *this /= std::to_string(array_index); + } + + /*! + @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + */ + friend json_pointer operator/(const json_pointer& left_ptr, const json_pointer& right_ptr) + { + return json_pointer(left_ptr) /= right_ptr; + } + + /*! + @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + */ + friend json_pointer operator/(const json_pointer& ptr, std::string token) + { + return json_pointer(ptr) /= std::move(token); + } + + /*! + @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + */ + friend json_pointer operator/(const json_pointer& lhs, std::size_t array_index) + { + return json_pointer(lhs) /= array_index; + } + + /*! + @brief create a new JSON pointer that is the parent of this JSON pointer + */ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + /*! @param[in] s reference token to be converted into an array index @@ -11918,12 +11979,12 @@ class json_pointer } /*! - @brief remove and return last reference pointer + @brief remove and return last reference token @throw out_of_range.405 if JSON pointer has no parent */ std::string pop_back() { - if (JSON_UNLIKELY(is_root())) + if (JSON_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } @@ -11934,24 +11995,29 @@ class json_pointer } /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent + @brief append an unescaped token at the end of the reference pointer */ - void push_back(const std::string& tok) + void push_back(const std::string& token) { - reference_tokens.push_back(tok); + reference_tokens.push_back(token); + } + + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) + { + reference_tokens.push_back(std::move(token)); } - private: /// return whether pointer points to the root document - bool is_root() const noexcept + bool empty() const noexcept { return reference_tokens.empty(); } + private: json_pointer top() const { - if (JSON_UNLIKELY(is_root())) + if (JSON_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } @@ -20002,7 +20068,7 @@ class basic_json const auto operation_add = [&result](json_pointer & ptr, basic_json val) { // adding to the root of the target document means replacing it - if (ptr.is_root()) + if (ptr.empty()) { result = val; } diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index 1bcd3938a7..349bdf6cb4 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -438,6 +438,7 @@ TEST_CASE("JSON pointers") }) { CHECK(json::json_pointer(ptr).to_string() == ptr); + CHECK(std::string(json::json_pointer(ptr)) == ptr); } } @@ -460,7 +461,7 @@ TEST_CASE("JSON pointers") } } - SECTION("push and pop") + SECTION("empty, push, pop and parent") { const json j = { @@ -489,22 +490,28 @@ TEST_CASE("JSON pointers") // empty json_pointer returns the root JSON-object auto ptr = ""_json_pointer; + CHECK(ptr.empty()); CHECK(j[ptr] == j); // simple field access ptr.push_back("pi"); + CHECK(!ptr.empty()); CHECK(j[ptr] == j["pi"]); ptr.pop_back(); + CHECK(ptr.empty()); CHECK(j[ptr] == j); // object and children access - ptr.push_back("answer"); + const std::string answer("answer"); + ptr.push_back(answer); ptr.push_back("everything"); + CHECK(!ptr.empty()); CHECK(j[ptr] == j["answer"]["everything"]); ptr.pop_back(); ptr.pop_back(); + CHECK(ptr.empty()); CHECK(j[ptr] == j); // push key which has to be encoded @@ -512,5 +519,78 @@ TEST_CASE("JSON pointers") ptr.push_back("/"); CHECK(j[ptr] == j["object"]["/"]); CHECK(ptr.to_string() == "/object/~1"); + + CHECK(j[ptr.parent_pointer()] == j["object"]); + ptr = ptr.parent_pointer().parent_pointer(); + CHECK(ptr.empty()); + CHECK(j[ptr] == j); + // parent-pointer of the empty json_pointer is empty + ptr = ptr.parent_pointer(); + CHECK(ptr.empty()); + CHECK(j[ptr] == j); + + CHECK_THROWS_WITH(ptr.pop_back(), + "[json.exception.out_of_range.405] JSON pointer has no parent"); + } + + SECTION("operators") + { + const json j = + { + {"", "Hello"}, + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + { + "answer", { + {"everything", 42} + } + }, + {"list", {1, 0, 2}}, + { + "object", { + {"currency", "USD"}, + {"value", 42.99}, + {"", "empty string"}, + {"/", "slash"}, + {"~", "tilde"}, + {"~1", "tilde1"} + } + } + }; + + // empty json_pointer returns the root JSON-object + auto ptr = ""_json_pointer; + CHECK(j[ptr] == j); + + // simple field access + ptr = ptr / "pi"; + CHECK(j[ptr] == j["pi"]); + + ptr.pop_back(); + CHECK(j[ptr] == j); + + // object and children access + const std::string answer("answer"); + ptr /= answer; + ptr = ptr / "everything"; + CHECK(j[ptr] == j["answer"]["everything"]); + + ptr.pop_back(); + ptr.pop_back(); + CHECK(j[ptr] == j); + + CHECK(ptr / ""_json_pointer == ptr); + CHECK(j["/answer"_json_pointer / "/everything"_json_pointer] == j["answer"]["everything"]); + + // list children access + CHECK(j["/list"_json_pointer / 1] == j["list"][1]); + + // push key which has to be encoded + ptr /= "object"; + ptr = ptr / "/"; + CHECK(j[ptr] == j["object"]["/"]); + CHECK(ptr.to_string() == "/object/~1"); } }