From 4c97cd0ff39c453043cf1b474bdbcacb1979d0f0 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 6 Jan 2026 06:06:23 +0000 Subject: [PATCH] erase_at_pointer --- include/boost/json/impl/pointer.ipp | 104 ++++++++++ include/boost/json/value.hpp | 13 ++ test/Jamfile | 1 + test/erase_at_pointer.cpp | 307 ++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 test/erase_at_pointer.cpp diff --git a/include/boost/json/impl/pointer.ipp b/include/boost/json/impl/pointer.ipp index d07c48082..74c476744 100644 --- a/include/boost/json/impl/pointer.ipp +++ b/include/boost/json/impl/pointer.ipp @@ -520,6 +520,110 @@ value::set_at_pointer( return try_set_at_pointer(sv, ref, opts).value(); } +bool +value::erase_at_pointer( + string_view sv, + system::error_code& ec) +{ + ec.clear(); + if(sv.empty()){ + BOOST_JSON_FAIL(ec, error::syntax); + return false; + } + + string_view previous_segment; + + string_view segment = detail::next_segment(sv, ec); + + auto result = this; + auto previous_result = this; + + while (true) + { + if (ec.failed()) + return false; + + if (!result) + { + BOOST_JSON_FAIL(ec, error::not_found); + return false; + } + + if( segment.empty() ) + break; + + previous_segment = segment; + previous_result = result; + + switch (result->kind()) + { + case boost::json::kind::object: { + auto& obj = result->get_object(); + + detail::pointer_token const token(segment); + segment = detail::next_segment(sv, ec); + if (ec.failed()) + return false; + + result = detail::if_contains_token(obj, token); + if( !result ) + { + BOOST_JSON_FAIL(ec, error::not_found); + return false; + } + break; + } + case boost::json::kind::array: { + auto const index = detail::parse_number_token(segment, ec); + segment = detail::next_segment(sv, ec); + if (ec.failed()) + return false; + + auto& arr = result->get_array(); + result = arr.if_contains(index); + if( !result ) + { + BOOST_JSON_FAIL(ec, error::past_the_end); + return false; + } + break; + } + default: { + BOOST_JSON_FAIL(ec, error::value_is_scalar); + return false; + } + } + } + + switch (previous_result->kind()) + { + case boost::json::kind::object: { + auto& obj = previous_result->get_object(); + detail::pointer_token const token(previous_segment); + key_value_pair* kv = detail::find_in_object(obj, token).first; + if (kv) { + obj.erase(kv); + return true; + } + return false; + } + case boost::json::kind::array: { + auto const index = detail::parse_number_token(previous_segment, ec); + auto& arr = previous_result->get_array(); + if (arr.if_contains(index)){ + arr.erase(arr.begin() + index); + return true; + } + return false; + } + default: { + BOOST_JSON_FAIL(ec, error::value_is_scalar); + return false; + } + } +} + + } // namespace json } // namespace boost diff --git a/include/boost/json/value.hpp b/include/boost/json/value.hpp index 3e93bcf28..4f7184bb5 100644 --- a/include/boost/json/value.hpp +++ b/include/boost/json/value.hpp @@ -3035,6 +3035,19 @@ class value //------------------------------------------------------ + /** Remove an element via JSON Pointer. + + @{ + */ + BOOST_JSON_DECL + bool + erase_at_pointer( + string_view sv, + system::error_code& ec); + + /// @} + //------------------------------------------------------ + /** Check if two values are equal. Two values are equal when they are the same kind and their referenced diff --git a/test/Jamfile b/test/Jamfile index 37aec9643..18e6174ae 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -23,6 +23,7 @@ local SOURCES = doc_uses_allocator.cpp doc_using_numbers.cpp double.cpp + erase_at_pointer.cpp error.cpp fwd.cpp json.cpp diff --git a/test/erase_at_pointer.cpp b/test/erase_at_pointer.cpp new file mode 100644 index 000000000..2e2736e4d --- /dev/null +++ b/test/erase_at_pointer.cpp @@ -0,0 +1,307 @@ +// +// Copyright (c) 2026 Roy Bellingan (tsmtgdi@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/json +// + +#include +#include +#include +#include "test_suite.hpp" +#include + +namespace boost { +namespace json { + +class erase_at_pointer_test +{ + value + testValueArray() const + { + auto raw = R"( +[ + { + "image" : { + "path" : "somewhere", + "size" : 9100 + }, + "comment" : [ + { + "text" : "this is cool", + "timestamp" : 123456, + "likes" : [ + { + "author" : "Coco", + "timestamp" : 123 + }, + { + "author" : "Izzy", + "timestamp" : 456 + } + ] + } + ] + } +] + +)"; + return boost::json::parse(raw); + } + + value + testValueObject() const + { + auto raw = R"( +{ + "comment" : { + "text" : "this is cool", + "timestamp" : 123456, + "likes" : [ + { + "author" : "Coco", + "timestamp" : 123 + }, + { + "author" : "Izzy", + "timestamp" : 456 + } + ] + } +} + + +)"; + return boost::json::parse(raw); + } + + +public: + void testObject1() + { + value json = testValueObject(); + value target = boost::json::parse(R"( +{ + "comment" : { + "text" : "this is cool", + "timestamp" : 123456 + } +} +)"); + boost::system::error_code ec; + bool res = json.erase_at_pointer("/comment/likes", ec); + BOOST_TEST(res); + BOOST_TEST(!ec); + BOOST_TEST(target == json); + } + + void + testArray1(){ + value json = testValueArray(); + value target = boost::json::parse(R"( +[ + { + "image" : { + "path" : "somewhere", + "size" : 9100 + } + } +] +)"); + boost::system::error_code ec; + bool res = json.erase_at_pointer("/0/comment", ec); + BOOST_TEST(res); + BOOST_TEST(!ec); + BOOST_TEST(target == json); + } + + void + testArray2(){ + value json = testValueArray(); + value target = boost::json::parse(R"( +[ + { + "image" : { + "path" : "somewhere", + "size" : 9100 + }, + "comment" : [ + { + "text" : "this is cool", + "timestamp" : 123456, + "likes" : [ + { + "author" : "Coco", + "timestamp" : 123 + } + ] + } + ] + } +] +)"); + boost::system::error_code ec; + bool res = json.erase_at_pointer("/0/comment/0/likes/1", ec); + BOOST_TEST(res); + BOOST_TEST(!ec); + BOOST_TEST(target == json); + + } + + + void + malformedPointer() + { + value const original = testValueArray(); + value copy = original; + + boost::system::error_code ec; + bool res = copy.erase_at_pointer("invalid", ec); + //it should fail + BOOST_TEST(!res); + + BOOST_TEST(ec == error::missing_slash); + //and the json should not have any change + BOOST_TEST(copy == original); + } + + void + testEmptyPointer() + { + value const original = testValueArray(); + value copy = original; + + boost::system::error_code ec; + bool res = copy.erase_at_pointer("", ec); + //it should fail + BOOST_TEST(!res); + + BOOST_TEST(ec); + //and the json should not have any change + BOOST_TEST(copy == original); + } + + void inexistent1() + { + value const original = testValueArray(); + value copy = original; + boost::system::error_code ec; + bool res = copy.erase_at_pointer("/1/image", ec); + + //no deletition + BOOST_TEST(!res); + + //and the json should not have any change + BOOST_TEST(copy == original); + } + + void + inexistent2() + { + value const original = testValueObject(); + value copy = original; + boost::system::error_code ec; + bool res = copy.erase_at_pointer("/something/inexistent", ec); + + //no deletition + BOOST_TEST(!res); + + //and the json should not have any change + BOOST_TEST(copy == original); + } + + void + doubleDelete() + { + value json = testValueArray(); + boost::system::error_code ec; + bool res = json.erase_at_pointer("/0/comment/0/text", ec); + BOOST_TEST(res); + + auto copy = json; + + //already deleted + res = json.erase_at_pointer("/0/comment/0/text", ec); + BOOST_TEST(!res); + + BOOST_TEST(copy == json); + } + + void + chained() + { + value json = testValueArray(); + + value target = boost::json::parse(R"( +[ + { + "image": {}, + "comment": [ + { + "likes": [ + {}, + {} + ] + } + ] + } +] +)"); + + boost::system::error_code ec; + bool res = json.erase_at_pointer("/0/comment/0/text", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/comment/0/timestamp", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/comment/0/likes/0/author", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/comment/0/likes/0/timestamp", ec); + BOOST_TEST(res); + + //the previous element 0 still exists but is empty + res = json.erase_at_pointer("/0/comment/0/likes/0/author", ec); + BOOST_TEST(!res); + + res = json.erase_at_pointer("/0/comment/0/likes/1/author", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/comment/0/likes/1/timestamp", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/image/path", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/image/size", ec); + BOOST_TEST(res); + + res = json.erase_at_pointer("/0/image/invalid", ec); + BOOST_TEST(!res); + + BOOST_TEST(json == target); + } + + void + run() + { + testEmptyPointer(); + malformedPointer(); + testArray1(); + testArray2(); + testObject1(); + inexistent1(); + inexistent2(); + doubleDelete(); + chained(); + } +}; + + +TEST_SUITE(erase_at_pointer_test, "boost.json.erase_at_pointer"); + +} // namespace json +} // namespace boost