From e5a0d9c53910cf9dea27d7e4acb05ebf583095bc Mon Sep 17 00:00:00 2001 From: pnut2022 Date: Thu, 10 Sep 2026 15:39:18 +0800 Subject: [PATCH] feat: add compatible root-key predicate overloads to ParseSchema * Constrain schema predicates to preserve legacy length overloads * Prototype root-key callback overload for ParseSchema --- include/sonic/dom/generic_document.h | 38 ++++++++++++++ include/sonic/dom/root_schema_filter.h | 71 ++++++++++++++++++++++++++ tests/parse_schema_selection_test.cpp | 58 +++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 include/sonic/dom/root_schema_filter.h create mode 100644 tests/parse_schema_selection_test.cpp diff --git a/include/sonic/dom/generic_document.h b/include/sonic/dom/generic_document.h index d3a0d22b..6ae2dd93 100644 --- a/include/sonic/dom/generic_document.h +++ b/include/sonic/dom/generic_document.h @@ -23,6 +23,7 @@ #include "sonic/dom/dynamicnode.h" #include "sonic/dom/json_pointer.h" #include "sonic/dom/parser.h" +#include "sonic/dom/root_schema_filter.h" namespace sonic_json { @@ -153,6 +154,43 @@ class GenericDocument : public NodeType { return parseSchemaImpl(data, len); } + // Callback overload: selects root members without materializing a template. + // Missing keys are absent; order and duplicates are preserved. Rejected + // values use the existing non-validating schema skip path. This overload + // replaces the document; the original template overload is unchanged. + // The predicate must not throw or retain references to the decoded key. + template ( + std::declval()(std::declval())))> + GenericDocument& ParseSchema(StringView json, const Predicate& keep) { + return ParseSchema(json.data(), json.size(), keep); + } + + template ( + std::declval()(std::declval())))> + GenericDocument& ParseSchema(const char* json, size_t len, + const Predicate& keep) { + destroyDom(); + Parser parser; + internal::RootSchemaFilter, Predicate> dom(*alloc_, + keep); + if (!dom.SetUp(StringView(json, len))) { + parse_result_ = kErrorNoMem; + return *this; + } + parse_result_ = allocateStringBuffer(json, len); + if (sonic_unlikely(HasParseError())) return *this; + parse_result_ = parser.Parse(str_, len, dom); + if (sonic_unlikely(dom.oom_)) { + parse_result_ = kErrorNoMem; + return *this; + } + if (sonic_unlikely(HasParseError())) return *this; + NodeType::operator=(std::move(dom.st_[0])); + return *this; + } + /** * @brief Parse by std::string * @param parseFlags combination of different ParseFlag. diff --git a/include/sonic/dom/root_schema_filter.h b/include/sonic/dom/root_schema_filter.h new file mode 100644 index 00000000..b31dd45a --- /dev/null +++ b/include/sonic/dom/root_schema_filter.h @@ -0,0 +1,71 @@ +/* + * Copyright 2022 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "sonic/string_view.h" + +namespace sonic_json { +namespace internal { + +// Root-only selection using the same SkipOne path as template ParseSchema. +// Rejected values are not fully validated. Nested retained values are complete. +template +class RootSchemaFilter : public Handler { + public: + template + RootSchemaFilter(Allocator& alloc, const Predicate& keep) + : Handler(alloc), keep_(keep) {} + + bool Key(StringView key) { + if (root_object_ && depth_ == 1 && !keep_(key)) return false; + return Handler::Key(key); + } + + bool StartObject() { + if (depth_ == 0) root_object_ = true; + ++depth_; + return Handler::StartObject(); + } + + bool StartArray() { + ++depth_; + return Handler::StartArray(); + } + + bool EndObject(uint32_t count) { + --depth_; + return Handler::EndObject(count); + } + + bool EndArray(uint32_t count) { + --depth_; + return Handler::EndArray(count); + } + + static constexpr bool check_key_return = true; + + private: + const Predicate& keep_; + size_t depth_{0}; + bool root_object_{false}; +}; + +} // namespace internal +} // namespace sonic_json diff --git a/tests/parse_schema_selection_test.cpp b/tests/parse_schema_selection_test.cpp new file mode 100644 index 00000000..a65c7f29 --- /dev/null +++ b/tests/parse_schema_selection_test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2022 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" +#include "sonic/sonic.h" + +TEST(ParseSchemaSelection, RootProjectionAndReuse) { + auto keep = [](sonic_json::StringView k) { + return k == "keep" || k == "also" || + (k.size() >= 9 && k.substr(0, 9) == "bid_type_"); + }; + const char* inputs[] = { + R"({"drop":42,"keep":{"x":[1,true]},"bid_type_7":3})", + R"({"keep":1,"keep":2,"also":3})", + R"({"drop":{"keep":42}})", R"({"keep":null})", + R"([{"drop":1}])", R"({"drop":1,"ke\u0065p":2})"}; + const char* expected[] = { + R"({"keep":{"x":[1,true]},"bid_type_7":3})", + R"({"keep":1,"keep":2,"also":3})", R"({})", + R"({"keep":null})", R"([{"drop":1}])", R"({"keep":2})"}; + sonic_json::Document d; + for (size_t i = 0; i < 6; ++i) { + d.ParseSchema(inputs[i], keep); + ASSERT_FALSE(d.HasParseError()); + EXPECT_EQ(d.Dump(), expected[i]); + } +} + +TEST(ParseSchemaSelection, ExistingLengthOverloads) { + sonic_json::Document d; + const char* json = R"({"keep":1})"; + const char* schema = R"({"keep":null})"; + d.Parse(schema); + d.ParseSchema(json, 10); + ASSERT_FALSE(d.HasParseError()); + EXPECT_EQ(d.Dump(), json); + d.Parse(schema); + d.ParseSchema(json, 10u); + ASSERT_FALSE(d.HasParseError()); + EXPECT_EQ(d.Dump(), json); + d.Parse(schema); + d.ParseSchema(json, size_t(10)); + ASSERT_FALSE(d.HasParseError()); + EXPECT_EQ(d.Dump(), json); +}