Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add operator/= and operator/ to construct a JSON pointer by appending two JSON pointers #1469

Merged
merged 6 commits into from
Mar 11, 2019
84 changes: 75 additions & 9 deletions include/nlohmann/detail/json_pointer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"));
}
Expand All @@ -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"));
}
Expand Down
2 changes: 1 addition & 1 deletion include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
86 changes: 76 additions & 10 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11886,6 +11886,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

Expand All @@ -11908,12 +11969,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"));
}
Expand All @@ -11924,24 +11985,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"));
}
Expand Down Expand Up @@ -19965,7 +20031,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;
}
Expand Down
84 changes: 82 additions & 2 deletions test/src/unit-json_pointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ TEST_CASE("JSON pointers")
})
{
CHECK(json::json_pointer(ptr).to_string() == ptr);
CHECK(std::string(json::json_pointer(ptr)) == ptr);
}
}

Expand All @@ -460,7 +461,7 @@ TEST_CASE("JSON pointers")
}
}

SECTION("push and pop")
SECTION("empty, push, pop and parent")
{
const json j =
{
Expand Down Expand Up @@ -489,28 +490,107 @@ 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
ptr.push_back("object");
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");
}
}