Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions include/sonic/dom/generic_document.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -153,6 +154,43 @@ class GenericDocument : public NodeType {
return parseSchemaImpl<parseFlags>(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 <ParseFlags parseFlags = ParseFlags::kParseDefault, typename Predicate,
typename = decltype(static_cast<bool>(
std::declval<const Predicate&>()(std::declval<StringView>())))>
GenericDocument& ParseSchema(StringView json, const Predicate& keep) {
return ParseSchema<parseFlags>(json.data(), json.size(), keep);
}

template <ParseFlags parseFlags = ParseFlags::kParseDefault, typename Predicate,
typename = decltype(static_cast<bool>(
std::declval<const Predicate&>()(std::declval<StringView>())))>
GenericDocument& ParseSchema(const char* json, size_t len,
const Predicate& keep) {
destroyDom();
Parser<parseFlags> parser;
internal::RootSchemaFilter<SAXHandler<NodeType>, 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.
Expand Down
71 changes: 71 additions & 0 deletions include/sonic/dom/root_schema_filter.h
Original file line number Diff line number Diff line change
@@ -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 <cstddef>
#include <cstdint>

#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 <typename Handler, typename Predicate>
class RootSchemaFilter : public Handler {
public:
template <typename Allocator>
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
58 changes: 58 additions & 0 deletions tests/parse_schema_selection_test.cpp
Original file line number Diff line number Diff line change
@@ -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<ParseFlags::kParseDefault>(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);
}