diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index da23e9bc2..c1a35afb9 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -100,3 +100,6 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to - Added in version 1.0.0. - Fixed in version 3.13.0 to also remove discarded values from a parent object; before, discarding an array or a value stored under an object key left a discarded member behind, which made the parse result serialize to invalid JSON. +- Fixed in version 3.13.0 so that discarding an array or object at its start event also hides its content from the + callback, as documented above; before, the callback was still called for the content, and the key of every member of + a discarded object was kept in memory until the parse ended. diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 962913610..8b8f544eb 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -582,8 +582,8 @@ class json_sax_dom_callback_parser bool start_object(std::size_t len) { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + // check callback for object start; not called inside a discarded container + const bool keep = keep_stack.back() && callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); // the key this object will be stored under, read before handle_value() @@ -619,6 +619,18 @@ class json_sax_dom_callback_parser bool key(string_t& val) { + if (!keep_stack.back() || !ref_stack.back()) + { + // the object is not stored: the value of this key is dropped in + // handle_value() without touching the key stacks + if (keep_stack.back()) + { + BasicJsonType k = BasicJsonType(val); + static_cast(callback(static_cast(ref_stack.size()), parse_event_t::key, k)); + } + return true; + } + BasicJsonType k = BasicJsonType(val); // check callback for the key @@ -704,7 +716,7 @@ class json_sax_dom_callback_parser bool start_array(std::size_t len) { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + const bool keep = keep_stack.back() && callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); // see start_object() diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8d48e643a..5a4595956 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12757,8 +12757,8 @@ class json_sax_dom_callback_parser bool start_object(std::size_t len) { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + // check callback for object start; not called inside a discarded container + const bool keep = keep_stack.back() && callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); // the key this object will be stored under, read before handle_value() @@ -12794,6 +12794,18 @@ class json_sax_dom_callback_parser bool key(string_t& val) { + if (!keep_stack.back() || !ref_stack.back()) + { + // the object is not stored: the value of this key is dropped in + // handle_value() without touching the key stacks + if (keep_stack.back()) + { + BasicJsonType k = BasicJsonType(val); + static_cast(callback(static_cast(ref_stack.size()), parse_event_t::key, k)); + } + return true; + } + BasicJsonType k = BasicJsonType(val); // check callback for the key @@ -12879,7 +12891,7 @@ class json_sax_dom_callback_parser bool start_array(std::size_t len) { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + const bool keep = keep_stack.back() && callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); // see start_object() diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 52b49c5d9..2d4ff6a38 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -1990,6 +1990,102 @@ TEST_CASE("parser class") } } + SECTION("no callback for the content of a discarded container (#5643)") + { + // discarding a container at its start event must also hide + // everything inside it from the callback: none of the nested + // keys, values, or nested containers' own start/end events may + // be reported + std::vector log; + bool first = true; + const json j = json::parse(R"({"skip": {"k1": 1, "k2": [2, {"k3": 3}]}, "keep": 1})", + [&](int depth, json::parse_event_t event, json & parsed) + { + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); + + if (depth == 1 && event == json::parse_event_t::object_start && first) + { + // discard "skip" right at its object_start event + first = false; + return false; + } + return true; + }); + + CHECK(log == std::vector + { + "0 object_start ", + "1 key \"skip\"", + "1 object_start ", + "1 key \"keep\"", + "1 value 1", + "0 object_end {\"keep\":1}" + }); + CHECK(j == json({{"keep", 1}})); + } + + SECTION("callback still called inside a container whose key was rejected (#5643)") + { + // rejecting a key does not discard its value's container at the + // container's own start event, so the callback is still called + // for that container's content; only storing the container + // under the rejected key is skipped + // (documented for parser_callback_t: "the callback is still + // called for the associated value, but its return value has no + // further effect") + const auto record = [](std::vector& log, int depth, json::parse_event_t event, const json & parsed) + { + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); + }; + + std::vector log_object; + const json j_object = json::parse(R"({"skip": {"k1": 1}, "keep": 2})", + [&](int depth, json::parse_event_t event, json & parsed) + { + record(log_object, depth, event, parsed); + return !(event == json::parse_event_t::key && parsed == json("skip")); + }); + + CHECK(log_object == std::vector + { + "0 object_start ", + "1 key \"skip\"", + "1 object_start ", + "2 key \"k1\"", + "2 value 1", + "1 key \"keep\"", + "1 value 2", + "0 object_end {\"keep\":2}" + }); + CHECK(j_object == json({{"keep", 2}})); + + // same for a rejected key whose value is an array rather than an object + std::vector log_array; + const json j_array = json::parse(R"({"skip": [1, {"k1": 2}], "keep": 2})", + [&](int depth, json::parse_event_t event, json & parsed) + { + record(log_array, depth, event, parsed); + return !(event == json::parse_event_t::key && parsed == json("skip")); + }); + + CHECK(log_array == std::vector + { + "0 object_start ", + "1 key \"skip\"", + "1 array_start ", + "2 value 1", + "2 object_start ", + "3 key \"k1\"", + "3 value 2", + "1 key \"keep\"", + "1 value 2", + "0 object_end {\"keep\":2}" + }); + CHECK(j_array == json({{"keep", 2}})); + } + SECTION("special cases") { // the following test cases cover the situation in which an empty