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
41 changes: 23 additions & 18 deletions cpp/src/arrow/json/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -526,16 +526,15 @@ class RawBuilderSet {

/// Appending null is slightly tricky since null count is stored inline
/// for builders of Kind::kNull. Append nulls using this helper
Status AppendNull(BuilderPtr parent, int field_index, BuilderPtr builder) {
Result<BuilderPtr> AppendNull(BuilderPtr parent, int field_index) {
BuilderPtr builder = parent.kind == Kind::kArray
? Cast<Kind::kArray>(parent)->value_builder()
: Cast<Kind::kObject>(parent)->field_builder(field_index);
if (ARROW_PREDICT_FALSE(!builder.nullable)) {
return ParseError("a required field was null");
}
switch (builder.kind) {
case Kind::kNull: {
DCHECK_EQ(builder, parent.kind == Kind::kArray
? Cast<Kind::kArray>(parent)->value_builder()
: Cast<Kind::kObject>(parent)->field_builder(field_index));

// increment null count stored inline
builder.index += 1;

Expand All @@ -545,38 +544,43 @@ class RawBuilderSet {
} else {
Cast<Kind::kObject>(parent)->field_builder(field_index, builder);
}
return Status::OK();
break;
}
case Kind::kBoolean:
return Cast<Kind::kBoolean>(builder)->AppendNull();
RETURN_NOT_OK(Cast<Kind::kBoolean>(builder)->AppendNull());
break;

case Kind::kNumber:
return Cast<Kind::kNumber>(builder)->AppendNull();
RETURN_NOT_OK(Cast<Kind::kNumber>(builder)->AppendNull());
break;

case Kind::kString:
return Cast<Kind::kString>(builder)->AppendNull();
RETURN_NOT_OK(Cast<Kind::kString>(builder)->AppendNull());
break;

case Kind::kNumberOrString: {
return Cast<Kind::kNumberOrString>(builder)->AppendNull();
RETURN_NOT_OK(Cast<Kind::kNumberOrString>(builder)->AppendNull());
break;
}

case Kind::kArray:
return Cast<Kind::kArray>(builder)->AppendNull();
RETURN_NOT_OK(Cast<Kind::kArray>(builder)->AppendNull());
break;

case Kind::kObject: {
auto struct_builder = Cast<Kind::kObject>(builder);
RETURN_NOT_OK(struct_builder->AppendNull());

for (int i = 0; i < struct_builder->num_fields(); ++i) {
auto field_builder = struct_builder->field_builder(i);
RETURN_NOT_OK(AppendNull(builder, i, field_builder));
RETURN_NOT_OK(AppendNull(builder, i));
}
return Status::OK();
break;
}

default:
return Status::NotImplemented("invalid builder Kind");
}
return builder;
}

Status Finish(const std::shared_ptr<Array>& scalar_values, BuilderPtr builder,
Expand Down Expand Up @@ -669,7 +673,9 @@ class ParseImpl : public BlockParser {

Status Null() {
DCHECK(!builder_stack_.empty());
return builder_set_.AppendNull(builder_stack_.back(), field_index_, builder_);
ARROW_ASSIGN_OR_RAISE(builder_,
builder_set_.AppendNull(builder_stack_.back(), field_index_));
return Status::OK();
Comment on lines +676 to +678

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot! :)

}

Status HandleUnexpectedField(std::string_view key, sj::value& value) {
Expand Down Expand Up @@ -962,11 +968,10 @@ class ParseImpl : public BlockParser {
if (!absent_fields_stack_[i]) {
continue;
}
auto field_builder = Cast<Kind::kObject>(parent)->field_builder(i);
if (ARROW_PREDICT_FALSE(!field_builder.nullable)) {
if (ARROW_PREDICT_FALSE(!Cast<Kind::kObject>(parent)->field_builder(i).nullable)) {
return ParseError("a required field was absent");
}
RETURN_NOT_OK(builder_set_.AppendNull(parent, i, field_builder));
RETURN_NOT_OK(builder_set_.AppendNull(parent, i));
}
absent_fields_stack_.Pop();
EndNested();
Expand Down
27 changes: 27 additions & 0 deletions cpp/src/arrow/json/parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,22 @@ TEST(BlockParserWithSchema, FailOnIncompleteJson) {
ASSERT_RAISES(Invalid, ParseFromString(options, "{\"a\":0, \"b\"", &parsed));
}

TEST(BlockParserWithSchema, NullsInList) {
auto options = ParseOptions::Defaults();
options.explicit_schema = schema({field("a", list(null()))});
for (auto behavior :
{UnexpectedFieldBehavior::Error, UnexpectedFieldBehavior::Ignore}) {
options.unexpected_field_behavior = behavior;
AssertParseColumns(options, R"({"a": [null, null]})", {field("a", list(null()))},
{"[[null, null]]"});
}

auto type = list(struct_({field("b", null())}));
options.explicit_schema = schema({field("a", type)});
AssertParseColumns(options, R"({"a": [{"b": null}, null]})", {field("a", type)},
{R"([[{"b": null}, null]])"});
}

TEST(BlockParserWithSchema, ValidateIgnoredFields) {
auto options = ParseOptions::Defaults();
options.explicit_schema = schema({field("known", int64())});
Expand Down Expand Up @@ -286,6 +302,17 @@ TEST(BlockParser, Null) {
R"([{"plain": null}, {"plain": null}])"});
}

TEST(BlockParser, NullsInList) {
auto options = ParseOptions::Defaults();
options.unexpected_field_behavior = UnexpectedFieldBehavior::InferType;
AssertParseColumns(options, R"({"a": [null, null], "b": [null, "hi", null]})",
{field("a", list(null())), field("b", list(utf8()))},
{"[[null, null]]", R"([[null, "hi", null]])"});
AssertParseColumns(options, R"({"a": [{"b": null}, null]})",
{field("a", list(struct_({field("b", null())})))},
{R"([[{"b": null}, null]])"});
}

TEST(BlockParser, InferNewFields) {
std::string src = R"(
{}
Expand Down
Loading