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
16 changes: 11 additions & 5 deletions include/sonic/internal/arch/simd_skip.h
Original file line number Diff line number Diff line change
Expand Up @@ -959,15 +959,21 @@ class SkipScanner2 {
}

if (found) {
// if not null
if (peek() != 'n') {
return getJsonPath<style, serializeFlags>(
path, index + 1, jsonGenerator, jsonGeneratorFactory);
} else {
// A null value only has to be skipped when the path continues past it,
// because nothing can be selected out of null. When the key is the last
// step, null is the result the caller asked for, so recurse and let the
// path_is_nil branch emit it -- exactly what the array element path
// already does for e.g. "$[0]" over "[null]".
// Under FLATTEN/QUOTE this call is collecting wildcard results, where
// nulls are deliberately dropped (see JsonPath.WildcardFiltersNull),
// so the skip must stay unconditional there.
if (peek() == 'n' && (style != RAW || index + 1 < path.size())) {
// skip null
RETURN_FALSE_IF_PARSE_ERROR(skipOne());
return false;
}
return getJsonPath<style, serializeFlags>(
path, index + 1, jsonGenerator, jsonGeneratorFactory);
}
return false;
}
Expand Down
50 changes: 49 additions & 1 deletion tests/jsonpath_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@ TEST(JsonPath, BadCases) {
TEST(JsonPath, KeyNullElementPair) {
auto json =
R"( { "last_name": "Kim", "first_name":"Sam", "gender": false, "income": null, "age": 38} )";
TestOk(json, "$.income", "");
TestOk(json, "$.income",
"null"); // null object member returned, not skipped (aligns with DOM)
}

TEST(JsonPath, KeyIntoStringValue) {
Expand Down Expand Up @@ -767,6 +768,53 @@ TEST(JsonPathDom, NotFoundAndUnsupportedPath) {
EXPECT_EQ(std::get<0>(unsupported), "");
}

TEST(JsonPathOnDemand, NullObjectMemberIsReturnedNotSkipped) {
// A null object member is a value, not a missing member. The on-demand
// backend skipped it whenever a key matched, so $.a over {"a":null} reported
// no match while the DOM backend returned "null" for the same input.
// The null only has to be skipped when the path continues past it, since
// nothing can be selected out of null.
struct Case {
const char* json;
const char* path;
};
for (const auto& c :
{Case{R"({"a":null})", "$.a"}, Case{R"({"a":null,"b":1})", "$.a"},
Case{R"({"b":1,"a":null})", "$.a"},
Case{R"({"a":{"b":null}})", "$.a.b"},
Case{R"({"a":[{"b":null}]})", "$.a[0].b"}}) {
auto od = GetByJsonPathOnDemand<kSerializeJavaStyleFlag>(c.json, c.path);
EXPECT_EQ(std::get<1>(od), kErrorNone) << c.json << " " << c.path;
EXPECT_EQ(std::get<0>(od), "null") << c.json << " " << c.path;
// Both backends must agree.
auto dom = GetByJsonPath(c.json, c.path);
EXPECT_EQ(std::get<1>(dom), std::get<1>(od)) << c.json << " " << c.path;
EXPECT_EQ(std::get<0>(dom), std::get<0>(od)) << c.json << " " << c.path;
}

// Arrays already behaved correctly; keep them covered so the two stay
// aligned.
for (const auto& c : {Case{"[null]", "$[0]"}, Case{"[1,null]", "$[1]"}}) {
auto od = GetByJsonPathOnDemand<kSerializeJavaStyleFlag>(c.json, c.path);
EXPECT_EQ(std::get<1>(od), kErrorNone) << c.json << " " << c.path;
EXPECT_EQ(std::get<0>(od), "null") << c.json << " " << c.path;
}

// The path continuing past a null must still not match.
for (const auto& c :
{Case{R"({"a":null})", "$.a.b"}, Case{R"({"a":null})", "$.a[0]"},
Case{R"({"a":{"b":null}})", "$.a.b.c"}}) {
auto od = GetByJsonPathOnDemand<kSerializeJavaStyleFlag>(c.json, c.path);
EXPECT_NE(std::get<1>(od), kErrorNone) << c.json << " " << c.path;
}

// Wildcard collection deliberately drops nulls; that must not change.
auto wc = GetByJsonPathOnDemand<kSerializeJavaStyleFlag>(
R"({"root": [{"a":null},{"a":"foo"},{"a":"bar"}]})", "$.root[*].a");
EXPECT_EQ(std::get<1>(wc), kErrorNone);
EXPECT_EQ(std::get<0>(wc), R"(["foo","bar"])");
}

TEST(JsonPathDom, NegativeIndexSupportedButOnDemandUnsupported) {
auto dom_got = GetByJsonPath("[0,1,2]", "$[-1]");
EXPECT_EQ(std::get<1>(dom_got), kErrorNone);
Expand Down