Skip to content

GH-51395: [C++][Docs] Replace RapidJSON with simdjson in row converter example - #51396

Open
Reranko05 wants to merge 8 commits into
apache:mainfrom
Reranko05:example-doc
Open

Reranko05 wants to merge 8 commits into
apache:mainfrom
Reranko05:example-doc

Conversation

@Reranko05

@Reranko05 Reranko05 commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Rationale for this change

This PR continues the simdjson migration by replacing RapidJSON usage in the C++ example demonstrating row-to-columnar and columnar-to-row conversion using simdjson and Apache Arrow.

Changes

  • Add simdjson_row_converter.cc for JSON parsing, Arrow array construction, and JSON serialization.
  • Delete rapidjson_row_converter.cc.
  • Update example build configuration and documentation.

Was AI used for this PR?

In accordance to the AI generation guidelines, please disclose below whether and how AI was used in this PR.

PR code and description written by:

  • Human
  • AI

Reviewed before submission by:

  • Human
  • AI
  • Not reviewed

@Reranko05 Reranko05 added the CI: Extra: C++ Run extra C++ CI label Sep 19, 2026
@Reranko05 Reranko05 changed the title GH-51395: [C++] Replace RapidJSON with Simdjson in row converter example GH-51395: [C++] Replace RapidJSON with simdjson in row converter example Sep 19, 2026
@Reranko05 Reranko05 changed the title GH-51395: [C++] Replace RapidJSON with simdjson in row converter example GH-51395: [C++][Docs] Replace RapidJSON with simdjson in row converter example Sep 19, 2026
@Reranko05

Copy link
Copy Markdown
Collaborator Author

Will mark this PR Ready to Review after #51038 is merged.

@Reranko05
Reranko05 marked this pull request as ready for review October 8, 2026 09:37
@Reranko05
Reranko05 requested a review from pitrou as a code owner October 8, 2026 09:37
Copilot AI balanced review requested due to automatic review settings October 8, 2026 09:37

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Reranko05
Reranko05 requested a review from kou October 8, 2026 09:37
// * arrow::TableBatchReader and Arrow iterators for converting Arrow tables back
// into row-based JSON data

namespace arrow {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Example code shouldn't be in the arrow namespace because it showcases how third-party code using Arrow C++ could look like.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, okay. I got it.

Comment on lines +41 to +42
// * arrow::internal::JsonWriter for writing JSON values
// * arrow::internal::ParseJsonObject and related helpers for parsing JSON

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Examples should not use any internal Arrow APIs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Okay, understood.

@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Oct 8, 2026
Copilot AI balanced review requested due to automatic review settings October 9, 2026 09:20

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI balanced review requested due to automatic review settings October 9, 2026 09:40

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Reranko05

Copy link
Copy Markdown
Collaborator Author

I have not got the time to review this myself after changing it into plain simdjson. I will mark it as Ready for review after I review this.

@Reranko05
Reranko05 marked this pull request as draft October 11, 2026 09:52
@Reranko05
Reranko05 marked this pull request as ready for review October 16, 2026 11:19
Copilot AI balanced review requested due to automatic review settings October 16, 2026 11:19

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Reranko05

Copy link
Copy Markdown
Collaborator Author

@pitrou @kou Could you review this when you have time? (CI failures seem unrelated)

Comment on lines +189 to +208
// Parse each row and append its values to the corresponding Arrow builders.
for (const auto& json : rows) {
ARROW_ASSIGN_OR_RAISE(auto element,
Unwrap(parser.parse(json), "Failed to parse JSON row"));
ARROW_ASSIGN_OR_RAISE(auto object,
Unwrap(element.get_object(), "Expected a JSON object"));

for (int i = 0; i < schema->num_fields(); ++i) {
const auto& field = schema->field(i);
arrow::ArrayBuilder* builder = batch_builder->GetField(i);

ARROW_ASSIGN_OR_RAISE(auto value, GetField(object, field->name()));

if (!value.has_value()) {
ARROW_RETURN_NOT_OK(builder->AppendNull());
} else {
ARROW_RETURN_NOT_OK(AppendJsonValue(*value, field->type(), builder));
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we keep column based iteration (not row based iteration) like the RapidJSON version so that we demonstrate how to use a visitor to convert row based data to Arrow data?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I changed the impl to do column based iteration.

for (int64_t i = 0; i < batch->num_rows(); ++i) {
ARROW_ASSIGN_OR_RAISE(auto row, ConvertRowToJson(*batch, i));
rows.push_back(std::make_shared<std::string>(std::move(row)));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we keep column based iteration (not row based iteration) like the RapidJSON version so that we demonstrate how to use a visitor to convert Arrow data to row based data?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

As said above.

@Reranko05

Copy link
Copy Markdown
Collaborator Author
[1342/2536] Building CXX object _deps/grpc-build/CMakeFiles/grpc.dir/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc.o
FAILED: [code=1] _deps/grpc-build/CMakeFiles/grpc.dir/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc.o 
/opt/homebrew/bin/ccache /usr/bin/c++ -DARROW_HAVE_NEON -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/third_party/address_sorting/include -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/src/core/ext/upb-gen -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/src/core/ext/upbdefs-gen -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/third_party/upb -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/third_party/utf8_range -I/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/third_party/xxhash -I/Users/runner/work/arrow/arrow/build/cpp/_deps/cares-build -I/Users/runner/work/arrow/arrow/build/cpp/_deps/cares-src -I/Users/runner/work/arrow/arrow/build/cpp/_deps/cares-src/include -isystem /Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/include -isystem /Users/runner/work/arrow/arrow/build/cpp/_deps/re2-src -isystem /Users/runner/work/arrow/arrow/build/cpp/zlib_ep/src/zlib_ep-install/include -isystem /Users/runner/work/arrow/arrow/build/cpp/_deps/absl-src -isystem /opt/homebrew/opt/openssl@4/include -fno-aligned-new  -I/opt/homebrew/Cellar/libiodbc/3.52.16/include -Qunused-arguments -fcolor-diagnostics  -Wall -Wno-unknown-warning-option -Wno-pass-failed -march=armv8-a  -Wno-attributes -Wno-format-security -Wno-unknown-warning-option  -g -O0 -ggdb -g1 -Wno-error -std=c++20 -arch arm64 -fPIC -Wno-error=deprecated-declarations -MD -MT _deps/grpc-build/CMakeFiles/grpc.dir/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc.o -MF _deps/grpc-build/CMakeFiles/grpc.dir/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc.o.d -o _deps/grpc-build/CMakeFiles/grpc.dir/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc.o -c /Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc
/Users/runner/work/arrow/arrow/build/cpp/_deps/grpc-src/src/core/credentials/transport/tls/grpc_tls_crl_provider.cc:59:14: error: cannot initialize a variable of type 'X509_NAME *' (aka 'X509_name_st *') with an rvalue of type 'const X509_NAME *' (aka 'const X509_name_st *')
   59 |   X509_NAME* issuer = X509_CRL_get_issuer(crl);
      |              ^        ~~~~~~~~~~~~~~~~~~~~~~~~

@kou @pitrou @taepper I am a bit confused about whether this failure is related to my PR, since another PR passes the same CI job even after I rebased mine. Could someone clarify?

@Reranko05

Copy link
Copy Markdown
Collaborator Author

BTW, I have not yet updated the .rst doc file, I will do that once the simdjson_row_converter.cc changes are approved.

@taepper

taepper commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Hi, yes, I can take a look at this

Can you quickly confirm my understanding re/ the commit history because I am a little bit confused (and the history has since been rebased & force-pushed)

The initial commit Replace example was the first draft and the subsequent commits (which change the structure considerably), were the iterations based on review feedback?

@taepper I am a bit confused about whether this failure is related to my PR, since another #54839 passes the same CI job even after I rebased mine. Could someone clarify?

Yes, I would also think that your change to CMakeLists.txt is unrelated

@pitrou

pitrou commented Oct 22, 2026

Copy link
Copy Markdown
Member

@kou @pitrou @taepper I am a bit confused about whether this failure is related to my PR

It's not, we see it on other PRs as well.

@Reranko05

Copy link
Copy Markdown
Collaborator Author

Oh okay, thanks for confirming :)
This is ready for review.
cc @kou @pitrou @taepper

@Reranko05

Copy link
Copy Markdown
Collaborator Author

were the iterations based on review feedback?

@taepper I initially used internal Arrow APIs to write the example code, then I got to know that I should write it in plain simdjson and then it was that my impl was row based but we needed column based iteration, so yeah they were considerable feedback changes 😅

@Reranko05

Copy link
Copy Markdown
Collaborator Author

Hi @kou @pitrou @taepper, could you please review this when you get a chance? Once this is reviewed, I will open the final cleanup PR (#55266). Thanks!

@kou kou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the documents and the PR description?

Comment thread cpp/examples/arrow/CMakeLists.txt Resolved
@github-actions github-actions Bot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Oct 26, 2026

@taepper taepper left a comment

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 actually found the row-based conversion to arrow more readable [and it would also allow usage of the simdjson's ondemand backend]. The visit on arrow arrays can still be demonstrated for the back-conversion to json in JsonColumnWriter.)

Splitting the two conversions json->arrow and arrow->json into two separate files would make this more well-organized? I would also imagine any user that wants to follow this code example in their own project should organise it in separate files as well

ARROW_RETURN_NOT_OK(
AppendJsonValue(*child, field->type(), builder->child_builder(i).get()));
// A JSON value, or std::nullopt if it is missing.
using JsonValue = std::optional<simdjson::dom::element>;

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.

Ugh, do we really need this special type? Can't we append a simdjson null document in those cases instead?

Also, the code comment below mentions:

Missing fields, and fields of null objects, are returned as std::nullopt

but I do not think this is true? The way I understand it is that fields of null objects are returned as null, but in case of missing fields this:

    ARROW_ASSIGN_OR_RAISE(auto object,
                          Unwrap(value->get_object(), "Failed to get JSON object"));
    ARROW_ASSIGN_OR_RAISE(auto child, GetField(object, name));

will raise an error instead?

Overall, I think avoiding this special type could make the example easier to understand

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I tested with a missing metrics field, and conversion succeeds, producing an Arrow null without errors. I think using a simdjson null value instead might require more code than the current std::optional approach. Could you explain how you'd implement this using the DOM API?

Copilot AI balanced review requested due to automatic review settings October 26, 2026 18:46

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@github-actions github-actions Bot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Oct 26, 2026
@Reranko05

Copy link
Copy Markdown
Collaborator Author

Could you update the documents and the PR description?

@kou Addressed feedbacks and updated docs and PR description.

@Reranko05

Copy link
Copy Markdown
Collaborator Author

(I actually found the row-based conversion to arrow more readable [and it would also allow usage of the simdjson's ondemand backend]. The visit on arrow arrays can still be demonstrated for the back-conversion to json in JsonColumnWriter.)

I am fine with any approach. @kou @pitrou what do you think?

Splitting the two conversions json->arrow and arrow->json into two separate files would make this more well-organized?

Same as above

Copilot AI balanced review requested due to automatic review settings October 26, 2026 19:14

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@taepper

taepper commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

I am not an Arrow member, so feel free to disregard my point-of-view. But in my opinion, for the example to be reusable by other code it should be a direct conversion to arrow structures, instead of doing a row-wise buffering into std::vector, which might not be possible for the user's use-case. So even the "columnar" conversion is actually a row-wise implementation. But instead of directly writing to arrow buffers (Builder types), we instead buffer first into std::vector (and this is not a simple buffering, but already recursively walking the entire documents) and we delay the actual conversion to later.

Additionally, the custom JsonValue type makes it easier to reuse parts of our example code. I pushed an example refactoring here:
https://github.com/taepper/arrow/blob/5247184017656960f6615a4f3befca1ca0f6fe9b/cpp/examples/arrow/json_to_arrow_converter.cc#L60-L168

Note that the actual conversion code got a lot briefer. Feel free to cherry pick (parts of) the changes.

Yet again, I do not want to block this PR unnecessarily, so if other reviewers feel differently, feel free to disregard

@Reranko05

Copy link
Copy Markdown
Collaborator Author

@taepper I agree with your point. Ultimately, though, I will leave the design decision to the Arrow members @pitrou @kou.

@kou

kou commented Oct 30, 2026

Copy link
Copy Markdown
Member

I checked the history. It seems that we wanted to show an example of how to convert row-based data to Apache Arrow data efficiently:

If simdjson isn't suitable for column-based conversion, we may keep using RapidJSON in this example. In that case, I want to stop bundling RapidJSON and use only system RapidJSON.

If simdjson is suitable for row-based conversion and it's fast, we may add a new example that shows fast row-based conversion. If row-based conversion is always faster than column-based conversion in any approach, we can remove the column-based conversion example. Otherwise, I think that we should keep the column-based conversion example.

@taepper

taepper commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

I tested the 3 different implementations (RapidJSON, column-wise simdjson, row-wise simdjson):

Median wall time (Ryzen 7 PRO 8700GE, GCC 13, Arrow release flags, single thread).

Dataset RapidJSON (main) simdjson, column-wise (a5d38cd) simdjson, row-wise (taepper@5247184)
JSON -> Arrow (parse + convert)
twitter.json (100 statuses as rows, 0.47 MB) 1.61 ms (wrong result) 0.68 ms 0.61 ms
canada.json (1 feature as row, 2.1 MB, 111k doubles in 3-level nested lists) segfault 9.50 ms 3.01 ms
example records (the 3 example rows repeated to 100k rows, 10.8 MB) 320 ms 133 ms 21.6 ms
Arrow -> JSON (to strings)
twitter.json 1.52 ms 0.69 ms 0.84 ms
canada.json segfault 41.7 ms 41.6 ms
example records 21,325 ms 33.9 ms 35.4 ms

@taepper

taepper commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

This advantage still holds for larger datasets:

Dataset RapidJSON (main) simdjson, column-wise (a5d38cd) simdjson, row-wise (taepper@5247184)
JSON -> Arrow (parse + convert)
twitter.json, 100 statuses x 100 = 10,000 rows, 47 MB 401 ms (wrong result) 104 ms 49 ms
canada.json, 1 feature x 100 = 100 rows, 209 MB segfault 1,115 ms 325 ms
canada.json, 1 feature x 1,000 = 1,000 rows, 2.1 GB segfault 9,550 ms 3,224 ms
example records, 3 rows x ~33k = 100,000 rows, 10.8 MB 319 ms 132 ms 22 ms
Arrow -> JSON (to strings)
twitter.json, 10,000 rows 642 ms 66 ms 68 ms
canada.json, 100 rows segfault 4,147 ms 4,169 ms
canada.json, 1,000 rows segfault 41,642 ms 41,896 ms
example records, 100,000 rows 21,074 ms 34.5 ms 34.3 ms

Also note peak memory reduced from 26 GB to 19 GB with the row-wise version

@Reranko05

Copy link
Copy Markdown
Collaborator Author

Thanks @taepper for sharing the benchmarks. It seems clear that row-based conversion is more efficient for the tested datasets. @kou, should we move forward with this approach and add a separate row-based simdjson example?

@kou

kou commented Nov 1, 2026

Copy link
Copy Markdown
Member

OK. Let's add an example that uses row-based approach with simdjson.

Let's revisit the current column-based approach example as a separated task. We may need another use case such as database not JSON for column-based approach. Or we may remove column-based approach entirely if row-based approach is better than column-based approach for non JSON use cases too.

@Reranko05

Copy link
Copy Markdown
Collaborator Author

Thanks, @kou.
@taepper, since you have already worked on the row-based implementation, would you be willing to open a PR for it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants