From a0a4e7cc0b2bab727b2b6e7505a52b0e7e38f063 Mon Sep 17 00:00:00 2001 From: trdesilva <5818730+trdesilva@users.noreply.github.com> Date: Tue, 12 May 2026 22:59:16 -0700 Subject: [PATCH] Add front methods to json_pointers (implements #4889) (#5152) * Added front, pop_front, and push_front methods to json_pointers in order to facilitate root-to-leaf traversals of JSON object trees. (#4889) Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> * undid VS autoformatting in irrelevant code Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> * Ran make amalgamate, added navigation to json_pointer's new front methods in mkdocs, and fixed errors in documented complexity for those methods. Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> * Fixed GCC 4.8 compile error caused by const iterators Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> * Fixed another gcc-4.8 compile error Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> * amalgamated Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> --------- Signed-off-by: trdesilva <5818730+trdesilva@users.noreply.github.com> --- docs/mkdocs/docs/api/json_pointer/front.md | 37 ++++++++++++++++++ docs/mkdocs/docs/api/json_pointer/index.md | 3 ++ .../mkdocs/docs/api/json_pointer/pop_front.md | 33 ++++++++++++++++ .../docs/api/json_pointer/push_front.md | 36 ++++++++++++++++++ .../docs/examples/json_pointer__front.cpp | 15 ++++++++ .../docs/examples/json_pointer__front.output | 2 + .../docs/examples/json_pointer__pop_front.cpp | 21 ++++++++++ .../examples/json_pointer__pop_front.output | 4 ++ .../examples/json_pointer__push_front.cpp | 21 ++++++++++ .../examples/json_pointer__push_front.output | 4 ++ docs/mkdocs/mkdocs.yml | 3 ++ include/nlohmann/detail/json_pointer.hpp | 38 +++++++++++++++++++ single_include/nlohmann/json.hpp | 38 +++++++++++++++++++ tests/src/unit-json_pointer.cpp | 21 +++++++++- 14 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 docs/mkdocs/docs/api/json_pointer/front.md create mode 100644 docs/mkdocs/docs/api/json_pointer/pop_front.md create mode 100644 docs/mkdocs/docs/api/json_pointer/push_front.md create mode 100644 docs/mkdocs/docs/examples/json_pointer__front.cpp create mode 100644 docs/mkdocs/docs/examples/json_pointer__front.output create mode 100644 docs/mkdocs/docs/examples/json_pointer__pop_front.cpp create mode 100644 docs/mkdocs/docs/examples/json_pointer__pop_front.output create mode 100644 docs/mkdocs/docs/examples/json_pointer__push_front.cpp create mode 100644 docs/mkdocs/docs/examples/json_pointer__push_front.output diff --git a/docs/mkdocs/docs/api/json_pointer/front.md b/docs/mkdocs/docs/api/json_pointer/front.md new file mode 100644 index 000000000..126f5ed90 --- /dev/null +++ b/docs/mkdocs/docs/api/json_pointer/front.md @@ -0,0 +1,37 @@ +# nlohmann::json_pointer::front + +```cpp +const string_t& front() const; +``` + +Return the first reference token. + +## Return value + +First reference token. + +## Exceptions + +Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. + +## Complexity + +Constant. + +## Examples + +??? example + + The example shows the usage of `front`. + + ```cpp + --8<-- "examples/json_pointer__front.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_pointer__front.output" + ``` + +## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/index.md b/docs/mkdocs/docs/api/json_pointer/index.md index 8b70e2de7..b1f895375 100644 --- a/docs/mkdocs/docs/api/json_pointer/index.md +++ b/docs/mkdocs/docs/api/json_pointer/index.md @@ -37,6 +37,9 @@ are the base for JSON patches. - [**pop_back**](pop_back.md) - remove the last reference token - [**back**](back.md) - return last reference token - [**push_back**](push_back.md) - append an unescaped token at the end of the pointer +- [**pop_front**](pop_front.md) - remove the first reference token +- [**front**](front.md) - return first reference token +- [**push_front**](push_front.md) - append an unescaped token at the start of the pointer - [**empty**](empty.md) - return whether the pointer points to the root document ## Literals diff --git a/docs/mkdocs/docs/api/json_pointer/pop_front.md b/docs/mkdocs/docs/api/json_pointer/pop_front.md new file mode 100644 index 000000000..a0bfc546a --- /dev/null +++ b/docs/mkdocs/docs/api/json_pointer/pop_front.md @@ -0,0 +1,33 @@ +# nlohmann::json_pointer::pop_front + +```cpp +void pop_front(); +``` + +Remove the first reference token. + +## Exceptions + +Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. + +## Complexity + +Linear in the number of reference tokens in the `json_pointer`. + +## Examples + +??? example + + The example shows the usage of `pop_front`. + + ```cpp + --8<-- "examples/json_pointer__pop_front.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_pointer__pop_front.output" + ``` + +## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/push_front.md b/docs/mkdocs/docs/api/json_pointer/push_front.md new file mode 100644 index 000000000..8d72f2363 --- /dev/null +++ b/docs/mkdocs/docs/api/json_pointer/push_front.md @@ -0,0 +1,36 @@ +# nlohmann::json_pointer::push_front + +```cpp +void push_front(const string_t& token); + +void push_front(string_t&& token); +``` + +Append an unescaped token at the start of the reference pointer. + +## Parameters + +`token` (in) +: token to add + +## Complexity + +Linear in the number of reference tokens in the `json_pointer`. + +## Examples + +??? example + + The example shows the result of `push_front` for different JSON Pointers. + + ```cpp + --8<-- "examples/json_pointer__push_front.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_pointer__push_front.output" + ``` + +## Version history diff --git a/docs/mkdocs/docs/examples/json_pointer__front.cpp b/docs/mkdocs/docs/examples/json_pointer__front.cpp new file mode 100644 index 000000000..42c76ffec --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__front.cpp @@ -0,0 +1,15 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // different JSON Pointers + json::json_pointer ptr1("/foo"); + json::json_pointer ptr2("/foo/0"); + + // call empty() + std::cout << "first reference token of \"" << ptr1 << "\" is \"" << ptr1.front() << "\"\n" + << "first reference token of \"" << ptr2 << "\" is \"" << ptr2.front() << "\"" << std::endl; +} diff --git a/docs/mkdocs/docs/examples/json_pointer__front.output b/docs/mkdocs/docs/examples/json_pointer__front.output new file mode 100644 index 000000000..f7a5adc80 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__front.output @@ -0,0 +1,2 @@ +first reference token of "/foo" is "foo" +first reference token of "/foo/0" is "foo" diff --git a/docs/mkdocs/docs/examples/json_pointer__pop_front.cpp b/docs/mkdocs/docs/examples/json_pointer__pop_front.cpp new file mode 100644 index 000000000..59403668c --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__pop_front.cpp @@ -0,0 +1,21 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create empty JSON Pointer + json::json_pointer ptr("/foo/bar/baz"); + std::cout << "\"" << ptr << "\"\n"; + + // call pop_front() + ptr.pop_front(); + std::cout << "\"" << ptr << "\"\n"; + + ptr.pop_front(); + std::cout << "\"" << ptr << "\"\n"; + + ptr.pop_front(); + std::cout << "\"" << ptr << "\"\n"; +} diff --git a/docs/mkdocs/docs/examples/json_pointer__pop_front.output b/docs/mkdocs/docs/examples/json_pointer__pop_front.output new file mode 100644 index 000000000..73d81b953 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__pop_front.output @@ -0,0 +1,4 @@ +"/foo/bar/baz" +"/bar/baz" +"/baz" +"" diff --git a/docs/mkdocs/docs/examples/json_pointer__push_front.cpp b/docs/mkdocs/docs/examples/json_pointer__push_front.cpp new file mode 100644 index 000000000..7bcdccb45 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__push_front.cpp @@ -0,0 +1,21 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create empty JSON Pointer + json::json_pointer ptr; + std::cout << "\"" << ptr << "\"\n"; + + // call push_front() + ptr.push_front("foo"); + std::cout << "\"" << ptr << "\"\n"; + + ptr.push_front("0"); + std::cout << "\"" << ptr << "\"\n"; + + ptr.push_front("bar"); + std::cout << "\"" << ptr << "\"\n"; +} diff --git a/docs/mkdocs/docs/examples/json_pointer__push_front.output b/docs/mkdocs/docs/examples/json_pointer__push_front.output new file mode 100644 index 000000000..79bf84946 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__push_front.output @@ -0,0 +1,4 @@ +"" +"/foo" +"/0/foo" +"/bar/0/foo" diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index ea089c7ea..ef6e8b505 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -233,6 +233,7 @@ nav: - '(Constructor)': api/json_pointer/json_pointer.md - 'back': api/json_pointer/back.md - 'empty': api/json_pointer/empty.md + - 'front': api/json_pointer/front.md - 'operator string_t': api/json_pointer/operator_string_t.md - 'operator==': api/json_pointer/operator_eq.md - 'operator!=': api/json_pointer/operator_ne.md @@ -240,7 +241,9 @@ nav: - 'operator/=': api/json_pointer/operator_slasheq.md - 'parent_pointer': api/json_pointer/parent_pointer.md - 'pop_back': api/json_pointer/pop_back.md + - 'pop_front': api/json_pointer/pop_front.md - 'push_back': api/json_pointer/push_back.md + - 'push_front': api/json_pointer/push_front.md - 'string_t': api/json_pointer/string_t.md - 'to_string': api/json_pointer/to_string.md - json_sax: diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index ac50a8703..0767484f8 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -154,6 +154,44 @@ class json_pointer return res; } + /// @brief remove first reference token + /// @sa https://json.nlohmann.me/api/json_pointer/pop_front/ + void pop_front() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + reference_tokens.erase(reference_tokens.begin()); + } + + /// @brief return first reference token + /// @sa https://json.nlohmann.me/api/json_pointer/front/ + const string_t& front() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + return reference_tokens.front(); + } + + /// @brief append an unescaped token at the start of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_front/ + void push_front(const string_t& token) + { + reference_tokens.insert(reference_tokens.begin(), token); + } + + /// @brief append an unescaped token at the start of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_front/ + void push_front(string_t&& token) + { + reference_tokens.insert(reference_tokens.begin(), std::move(token)); + } + /// @brief remove last reference token /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ void pop_back() diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c1363f90f..a8da603c7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -14872,6 +14872,44 @@ class json_pointer return res; } + /// @brief remove first reference token + /// @sa https://json.nlohmann.me/api/json_pointer/pop_front/ + void pop_front() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + reference_tokens.erase(reference_tokens.begin()); + } + + /// @brief return first reference token + /// @sa https://json.nlohmann.me/api/json_pointer/front/ + const string_t& front() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + return reference_tokens.front(); + } + + /// @brief append an unescaped token at the start of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_front/ + void push_front(const string_t& token) + { + reference_tokens.insert(reference_tokens.begin(), token); + } + + /// @brief append an unescaped token at the start of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_front/ + void push_front(string_t&& token) + { + reference_tokens.insert(reference_tokens.begin(), std::move(token)); + } + /// @brief remove last reference token /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ void pop_back() diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index e1522c9f7..a8ed4a89e 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -564,6 +564,14 @@ TEST_CASE("JSON pointers") CHECK(!ptr.empty()); CHECK(j[ptr] == j["answer"]["everything"]); + ptr.pop_front(); + CHECK(ptr.front() == "everything"); + ptr.pop_front(); + CHECK(ptr.empty()); + ptr.push_front("everything"); + ptr.push_front(answer); + CHECK(j[ptr] == j["answer"]["everything"]); + // check access via const pointer const auto cptr = ptr; CHECK(cptr.back() == "everything"); @@ -588,8 +596,17 @@ TEST_CASE("JSON pointers") CHECK(ptr.empty()); CHECK(j[ptr] == j); - CHECK_THROWS_WITH(ptr.pop_back(), - "[json.exception.out_of_range.405] JSON pointer has no parent"); + CHECK_THROWS_WITH_AS(ptr.pop_back(), + "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); + + CHECK_THROWS_WITH_AS(ptr.back(), + "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); + + CHECK_THROWS_WITH_AS(ptr.pop_front(), + "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); + + CHECK_THROWS_WITH_AS(ptr.front(), + "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); } SECTION("operators")