From a5db8c8492c1c3887cdc8766216e4b44a190b848 Mon Sep 17 00:00:00 2001 From: Advit Arora Date: Tue, 25 Aug 2026 11:32:25 +0530 Subject: [PATCH 1/2] GH-47417: [C++] Fix JSON parser dropping nulls at the start of a list --- cpp/src/arrow/json/parser.cc | 37 ++++++++++++++++--------------- cpp/src/arrow/json/parser_test.cc | 8 +++++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/cpp/src/arrow/json/parser.cc b/cpp/src/arrow/json/parser.cc index 53f856d8012e..1d50b13cca1b 100644 --- a/cpp/src/arrow/json/parser.cc +++ b/cpp/src/arrow/json/parser.cc @@ -529,50 +529,51 @@ 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) { - if (ARROW_PREDICT_FALSE(!builder.nullable)) { + Status AppendNull(BuilderPtr parent, int field_index, BuilderPtr* builder) { + if (ARROW_PREDICT_FALSE(!builder->nullable)) { return ParseError("a required field was null"); } - switch (builder.kind) { + switch (builder->kind) { case Kind::kNull: { - DCHECK_EQ(builder, parent.kind == Kind::kArray - ? Cast(parent)->value_builder() - : Cast(parent)->field_builder(field_index)); + DCHECK_EQ(*builder, + parent.kind == Kind::kArray + ? Cast(parent)->value_builder() + : Cast(parent)->field_builder(field_index)); // increment null count stored inline - builder.index += 1; + builder->index += 1; // update the parent, since changing builder doesn't affect parent if (parent.kind == Kind::kArray) { - Cast(parent)->value_builder(builder); + Cast(parent)->value_builder(*builder); } else { - Cast(parent)->field_builder(field_index, builder); + Cast(parent)->field_builder(field_index, *builder); } return Status::OK(); } case Kind::kBoolean: - return Cast(builder)->AppendNull(); + return Cast(*builder)->AppendNull(); case Kind::kNumber: - return Cast(builder)->AppendNull(); + return Cast(*builder)->AppendNull(); case Kind::kString: - return Cast(builder)->AppendNull(); + return Cast(*builder)->AppendNull(); case Kind::kNumberOrString: { - return Cast(builder)->AppendNull(); + return Cast(*builder)->AppendNull(); } case Kind::kArray: - return Cast(builder)->AppendNull(); + return Cast(*builder)->AppendNull(); case Kind::kObject: { - auto struct_builder = Cast(builder); + auto struct_builder = Cast(*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, &field_builder)); } return Status::OK(); } @@ -672,7 +673,7 @@ class HandlerBase : public BlockParser, /// /// @{ bool Null() { - status_ = builder_set_.AppendNull(builder_stack_.back(), field_index_, builder_); + status_ = builder_set_.AppendNull(builder_stack_.back(), field_index_, &builder_); return status_.ok(); } @@ -869,7 +870,7 @@ class HandlerBase : public BlockParser, if (ARROW_PREDICT_FALSE(!field_builder.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, &field_builder)); } absent_fields_stack_.Pop(); EndNested(); diff --git a/cpp/src/arrow/json/parser_test.cc b/cpp/src/arrow/json/parser_test.cc index 1b107aa020fd..802d353785dd 100644 --- a/cpp/src/arrow/json/parser_test.cc +++ b/cpp/src/arrow/json/parser_test.cc @@ -267,6 +267,14 @@ 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]])"}); +} + TEST(BlockParser, InferNewFields) { std::string src = R"( {} From 609dc0958a2a9897190cb4c74f973b4cb57b8e12 Mon Sep 17 00:00:00 2001 From: Advit Arora Date: Thu, 3 Sep 2026 10:39:59 +0530 Subject: [PATCH 2/2] GH-47417: [C++] Cover the explicit list(null()) schema under Error and Ignore --- cpp/src/arrow/json/parser_test.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cpp/src/arrow/json/parser_test.cc b/cpp/src/arrow/json/parser_test.cc index 802d353785dd..e10b6c73f9db 100644 --- a/cpp/src/arrow/json/parser_test.cc +++ b/cpp/src/arrow/json/parser_test.cc @@ -235,6 +235,17 @@ 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]]"}); + } +} + TEST(BlockParser, Basics) { auto options = ParseOptions::Defaults(); options.unexpected_field_behavior = UnexpectedFieldBehavior::InferType;