From a06e7f5d80af9b5a97f4a3ddcc25dd97c0b1d9dc Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 24 Jan 2019 16:46:51 +0100 Subject: [PATCH 1/6] JSON-pointer: add operator+() returning a new json_pointer --- include/nlohmann/detail/json_pointer.hpp | 23 +++++++++- single_include/nlohmann/json.hpp | 23 +++++++++- test/src/unit-json_pointer.cpp | 54 ++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 033625bc4c..705346c50c 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -114,14 +114,33 @@ class json_pointer } /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent + @brief append a token at the end of the reference pointer */ void push_back(const std::string& tok) { reference_tokens.push_back(tok); } + /*! + @brief append a key-token at the end of the reference pointer and return a new json-pointer. + */ + json_pointer operator+(const std::string& tok) const + { + auto ptr = *this; + ptr.push_back(tok); + return ptr; + } + + /*! + @brief append a array-index-token at the end of the reference pointer and return a new json-pointer. + */ + json_pointer operator+(const size_t& index) const + { + auto ptr = *this; + ptr.push_back(std::to_string(index)); + return ptr; + } + private: /// return whether pointer points to the root document bool is_root() const noexcept diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index ee72531b7b..d845f77ee4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11924,14 +11924,33 @@ class json_pointer } /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent + @brief append a token at the end of the reference pointer */ void push_back(const std::string& tok) { reference_tokens.push_back(tok); } + /*! + @brief append a key-token at the end of the reference pointer and return a new json-pointer. + */ + json_pointer operator+(const std::string& tok) const + { + auto ptr = *this; + ptr.push_back(tok); + return ptr; + } + + /*! + @brief append a array-index-token at the end of the reference pointer and return a new json-pointer. + */ + json_pointer operator+(const size_t& index) const + { + auto ptr = *this; + ptr.push_back(std::to_string(index)); + return ptr; + } + private: /// return whether pointer points to the root document bool is_root() const noexcept diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index 1bcd3938a7..373b2c2183 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -513,4 +513,58 @@ TEST_CASE("JSON pointers") CHECK(j[ptr] == j["object"]["/"]); CHECK(ptr.to_string() == "/object/~1"); } + + 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 + ptr = ptr + "answer"; + ptr = ptr + "everything"; + CHECK(j[ptr] == j["answer"]["everything"]); + + ptr.pop_back(); + ptr.pop_back(); + CHECK(j[ptr] == j); + + // push key which has to be encoded + ptr = ptr + "object"; + ptr = ptr + "/"; + CHECK(j[ptr] == j["object"]["/"]); + CHECK(ptr.to_string() == "/object/~1"); + } } From c850e9d82db4af16d995a455d8c0d20fb4c8448d Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Thu, 31 Jan 2019 19:15:36 +0000 Subject: [PATCH 2/6] Add operator/= and operator/ to construct a JSON pointer by appending two JSON pointers, as well as convenience op/= and op= to append a single unescaped token or array index; inspired by std::filesystem::path --- include/nlohmann/detail/json_pointer.hpp | 74 +++++++++++++++++------- single_include/nlohmann/json.hpp | 74 +++++++++++++++++------- test/src/unit-json_pointer.cpp | 20 +++++-- 3 files changed, 120 insertions(+), 48 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 705346c50c..8ec4179e2d 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -76,6 +76,52 @@ 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; + } + /*! @param[in] s reference token to be converted into an array index @@ -98,7 +144,7 @@ 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() @@ -114,31 +160,17 @@ class json_pointer } /*! - @brief append a token at the end of the reference pointer - */ - void push_back(const std::string& tok) - { - reference_tokens.push_back(tok); - } - - /*! - @brief append a key-token at the end of the reference pointer and return a new json-pointer. + @brief append an unescaped token at the end of the reference pointer */ - json_pointer operator+(const std::string& tok) const + void push_back(const std::string& token) { - auto ptr = *this; - ptr.push_back(tok); - return ptr; + reference_tokens.push_back(token); } - /*! - @brief append a array-index-token at the end of the reference pointer and return a new json-pointer. - */ - json_pointer operator+(const size_t& index) const + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) { - auto ptr = *this; - ptr.push_back(std::to_string(index)); - return ptr; + reference_tokens.push_back(std::move(token)); } private: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d845f77ee4..dd7236c842 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11886,6 +11886,52 @@ 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; + } + /*! @param[in] s reference token to be converted into an array index @@ -11908,7 +11954,7 @@ 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() @@ -11924,31 +11970,17 @@ class json_pointer } /*! - @brief append a token at the end of the reference pointer - */ - void push_back(const std::string& tok) - { - reference_tokens.push_back(tok); - } - - /*! - @brief append a key-token at the end of the reference pointer and return a new json-pointer. + @brief append an unescaped token at the end of the reference pointer */ - json_pointer operator+(const std::string& tok) const + void push_back(const std::string& token) { - auto ptr = *this; - ptr.push_back(tok); - return ptr; + reference_tokens.push_back(token); } - /*! - @brief append a array-index-token at the end of the reference pointer and return a new json-pointer. - */ - json_pointer operator+(const size_t& index) const + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) { - auto ptr = *this; - ptr.push_back(std::to_string(index)); - return ptr; + reference_tokens.push_back(std::move(token)); } private: diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index 373b2c2183..b16603a7b9 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -499,7 +499,8 @@ TEST_CASE("JSON pointers") 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(j[ptr] == j["answer"]["everything"]); @@ -546,24 +547,31 @@ TEST_CASE("JSON pointers") CHECK(j[ptr] == j); // simple field access - ptr = ptr + "pi"; + ptr = ptr / "pi"; CHECK(j[ptr] == j["pi"]); ptr.pop_back(); CHECK(j[ptr] == j); // object and children access - ptr = ptr + "answer"; - ptr = ptr + "everything"; + 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 = ptr + "object"; - ptr = ptr + "/"; + ptr /= "object"; + ptr = ptr / "/"; CHECK(j[ptr] == j["object"]["/"]); CHECK(ptr.to_string() == "/object/~1"); } From 5da757bbb346749dba6bc21296f8c9e84b1adbda Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Thu, 31 Jan 2019 23:39:12 +0000 Subject: [PATCH 3/6] Attempt to satisfy Coveralls by adding a test for (unchanged) operator std::string --- test/src/unit-json_pointer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index b16603a7b9..765c6b4894 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); } } From 164e0e54d97b445b39436ee337df64b75bbd0f60 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Mon, 25 Feb 2019 08:57:12 +0000 Subject: [PATCH 4/6] Rename private json_pointer::is_root as public json_pointer::empty for consistency with std::filesystem::path --- include/nlohmann/detail/json_pointer.hpp | 8 ++++---- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 8ec4179e2d..1efb504974 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -149,7 +149,7 @@ class json_pointer */ 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")); } @@ -173,16 +173,16 @@ class json_pointer 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 d13f8a9e8d..b10c9f5b27 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7466,7 +7466,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 dd7236c842..f3688a1cad 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11959,7 +11959,7 @@ class json_pointer */ 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")); } @@ -11983,16 +11983,16 @@ class json_pointer 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")); } @@ -20016,7 +20016,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; } From 08de9eeaca3b1b757586f32ff1932a62322f17e5 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Mon, 25 Feb 2019 09:10:45 +0000 Subject: [PATCH 5/6] Add json_pointer::parent_pointer (cf. std::filesystem::path::parent_path) --- include/nlohmann/detail/json_pointer.hpp | 15 +++++++++++++++ single_include/nlohmann/json.hpp | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 1efb504974..28a7e8f317 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -122,6 +122,21 @@ class json_pointer 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 diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f3688a1cad..15ec3b86f1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11932,6 +11932,21 @@ class json_pointer 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 From d183bd0456e9ef459b1a26179a0215f4817179e6 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Mon, 25 Feb 2019 09:25:02 +0000 Subject: [PATCH 6/6] Tests for json_pointer::empty and json_pointer::parent_pointer --- test/src/unit-json_pointer.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index 765c6b4894..349bdf6cb4 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -461,7 +461,7 @@ TEST_CASE("JSON pointers") } } - SECTION("push and pop") + SECTION("empty, push, pop and parent") { const json j = { @@ -490,23 +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 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 @@ -514,6 +519,18 @@ 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")