From 2b7f64e424e46d2eb8a599072770749e4fb0e0f8 Mon Sep 17 00:00:00 2001 From: Aaditya Srinivasan Date: Sun, 12 Jul 2026 00:17:10 +0530 Subject: [PATCH 1/3] GH-35460: Add JsonWriter wrapper for simdjson builder API --- cpp/src/arrow/CMakeLists.txt | 1 + cpp/src/arrow/json/CMakeLists.txt | 4 +- cpp/src/arrow/json/json_writer.cc | 124 +++++++++++++++++++ cpp/src/arrow/json/json_writer.h | 67 +++++++++++ cpp/src/arrow/json/json_writer_test.cc | 157 +++++++++++++++++++++++++ 5 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 cpp/src/arrow/json/json_writer.cc create mode 100644 cpp/src/arrow/json/json_writer.h create mode 100644 cpp/src/arrow/json/json_writer_test.cc diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index e792574a183..19551737a3c 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -1041,6 +1041,7 @@ if(ARROW_JSON) json/from_string.cc json/object_parser.cc json/object_writer.cc + json/json_writer.cc json/parser.cc json/reader.cc) foreach(ARROW_JSON_TARGET ${ARROW_JSON_TARGETS}) diff --git a/cpp/src/arrow/json/CMakeLists.txt b/cpp/src/arrow/json/CMakeLists.txt index b5b47c0ed37..b0e95bfe040 100644 --- a/cpp/src/arrow/json/CMakeLists.txt +++ b/cpp/src/arrow/json/CMakeLists.txt @@ -24,10 +24,12 @@ add_arrow_test(test parser_test.cc reader_test.cc object_parser_test.cc + json_writer_test.cc PREFIX "arrow-json" EXTRA_LINK_LIBS - RapidJSON) + RapidJSON + simdjson::simdjson) add_arrow_benchmark(parser_benchmark PREFIX diff --git a/cpp/src/arrow/json/json_writer.cc b/cpp/src/arrow/json/json_writer.cc new file mode 100644 index 00000000000..7b7a5b22c6c --- /dev/null +++ b/cpp/src/arrow/json/json_writer.cc @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "arrow/json/json_writer.h" + +namespace arrow::json { + +void JsonWriter::StartObject() { + MaybeComma(); + builder_.start_object(); + needs_comma_ = false; +} + +void JsonWriter::EndObject() { + builder_.end_object(); + needs_comma_ = true; +} + +void JsonWriter::StartArray() { + MaybeComma(); + builder_.start_array(); + needs_comma_ = false; +} + +void JsonWriter::EndArray() { + builder_.end_array(); + needs_comma_ = true; +} + +void JsonWriter::Key(std::string_view key) { + MaybeComma(); + builder_.escape_and_append_with_quotes(key); + builder_.append_colon(); + needs_comma_ = false; +} + +void JsonWriter::String(std::string_view value) { + MaybeComma(); + builder_.escape_and_append_with_quotes(value); + needs_comma_ = true; +} + +void JsonWriter::String(const char* data, size_t length) { + String(std::string_view(data, length)); +} + +void JsonWriter::RawValue(std::string_view value) { + MaybeComma(); + builder_.append_raw(value); + needs_comma_ = true; +} + +void JsonWriter::Bool(bool value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Int(int32_t value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Int64(int64_t value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Uint(uint32_t value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Uint64(uint64_t value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Double(double value) { + MaybeComma(); + builder_.append(value); + needs_comma_ = true; +} + +void JsonWriter::Null() { + MaybeComma(); + builder_.append_null(); + needs_comma_ = true; +} + +std::string_view JsonWriter::GetString() const { + return builder_.view().value(); +} + +void JsonWriter::Clear() { + builder_.clear(); + needs_comma_ = false; +} + +void JsonWriter::MaybeComma() { + if (needs_comma_) { + builder_.append_comma(); + } +} + +} // namespace arrow::json diff --git a/cpp/src/arrow/json/json_writer.h b/cpp/src/arrow/json/json_writer.h new file mode 100644 index 00000000000..f11516f0e69 --- /dev/null +++ b/cpp/src/arrow/json/json_writer.h @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include +#include + +#include "arrow/util/visibility.h" + +namespace arrow::json { + +class ARROW_EXPORT JsonWriter { + public: + JsonWriter() = default; + + void StartObject(); + void EndObject(); + + void StartArray(); + void EndArray(); + + void Key(std::string_view key); + + void String(std::string_view value); + void String(const char* data, size_t length); + void RawValue(std::string_view value); + void Bool(bool value); + + void Int(int32_t value); + void Int64(int64_t value); + + void Uint(uint32_t value); + void Uint64(uint64_t value); + + void Double(double value); + + void Null(); + + std::string_view GetString() const; + + void Clear(); + + private: + void MaybeComma(); + + simdjson::builder::string_builder builder_; + bool needs_comma_ = false; +}; + +} // namespace arrow::json diff --git a/cpp/src/arrow/json/json_writer_test.cc b/cpp/src/arrow/json/json_writer_test.cc new file mode 100644 index 00000000000..12ff2aac42c --- /dev/null +++ b/cpp/src/arrow/json/json_writer_test.cc @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include "arrow/json/json_writer.h" + +namespace arrow::json { + +TEST(JsonWriter, SimpleObject) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("a"); + writer.Int(42); + writer.Key("b"); + writer.String("hello"); + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), R"({"a":42,"b":"hello"})"); +} + +TEST(JsonWriter, Array) { + JsonWriter writer; + + writer.StartArray(); + writer.Int(1); + writer.Int(2); + writer.Int(3); + writer.EndArray(); + + EXPECT_EQ(writer.GetString(), "[1,2,3]"); +} + +TEST(JsonWriter, NestedObject) { + JsonWriter writer; + + writer.StartObject(); + + writer.Key("child"); + writer.StartObject(); + writer.Key("x"); + writer.Bool(true); + writer.EndObject(); + + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), + R"({"child":{"x":true}})"); +} + +TEST(JsonWriter, NullValue) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("value"); + writer.Null(); + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), + R"({"value":null})"); +} + +TEST(JsonWriter, DoubleValue) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("pi"); + writer.Double(3.14); + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), + R"({"pi":3.14})"); +} + +TEST(JsonWriter, UnsignedValues) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("u32"); + writer.Uint(42); + writer.Key("u64"); + writer.Uint64(1234567890123ULL); + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), + R"({"u32":42,"u64":1234567890123})"); +} + +TEST(JsonWriter, Int64Value) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("i64"); + writer.Int64(-1234567890123LL); + writer.EndObject(); + + EXPECT_EQ(writer.GetString(), + R"({"i64":-1234567890123})"); +} + +TEST(JsonWriter, Clear) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("a"); + writer.Int(1); + writer.EndObject(); + + writer.Clear(); + + writer.StartArray(); + writer.Int(5); + writer.EndArray(); + + EXPECT_EQ(writer.GetString(), "[5]"); +} + +TEST(JsonWriter, RawValue) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("number"); + writer.RawValue("123.456"); + writer.EndObject(); + + ASSERT_EQ(writer.GetString(), R"({"number":123.456})"); +} + +TEST(JsonWriter, StringWithExplicitLength) { + JsonWriter writer; + + const char value[] = {'a', 'b', 'c', 'd', 'e'}; + + writer.StartObject(); + writer.Key("value"); + writer.String(value, 3); + writer.EndObject(); + + ASSERT_EQ(writer.GetString(), R"({"value":"abc"})"); +} + +} // namespace arrow::json From c983eb0e6b72bf6b8c9f3d1b4b295b2d93860523 Mon Sep 17 00:00:00 2001 From: Aaditya Srinivasan Date: Sun, 12 Jul 2026 01:54:30 +0530 Subject: [PATCH 2/3] GH-35460: Migrate integration RapidJSON writer to JsonWriter --- ci/docker/ubuntu-24.04-cpp.dockerfile | 1 + cpp/src/arrow/CMakeLists.txt | 3 +- cpp/src/arrow/integration/CMakeLists.txt | 7 +++- cpp/src/arrow/integration/json_integration.cc | 35 +++++++++---------- .../integration/json_integration_test.cc | 12 +++---- cpp/src/arrow/integration/json_internal.cc | 31 ++++++++-------- cpp/src/arrow/integration/json_internal.h | 17 +++++---- cpp/src/arrow/json/json_writer.cc | 4 +-- cpp/src/arrow/json/json_writer_test.cc | 15 +++----- 9 files changed, 64 insertions(+), 61 deletions(-) diff --git a/ci/docker/ubuntu-24.04-cpp.dockerfile b/ci/docker/ubuntu-24.04-cpp.dockerfile index ce2a30d980a..00cc8796c54 100644 --- a/ci/docker/ubuntu-24.04-cpp.dockerfile +++ b/ci/docker/ubuntu-24.04-cpp.dockerfile @@ -221,4 +221,5 @@ ENV absl_SOURCE=BUNDLED \ PARQUET_BUILD_EXECUTABLES=ON \ PATH=/usr/lib/ccache/:$PATH \ PYTHON=python3 \ + simdjson_SOURCE=BUNDLED \ xsimd_SOURCE=BUNDLED diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index 19551737a3c..406247213ca 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -733,7 +733,8 @@ if(ARROW_BUILD_INTEGRATION OR ARROW_BUILD_TESTS) arrow_add_object_library(ARROW_INTEGRATION integration/json_integration.cc integration/json_internal.cc) foreach(ARROW_INTEGRATION_TARGET ${ARROW_INTEGRATION_TARGETS}) - target_link_libraries(${ARROW_INTEGRATION_TARGET} PRIVATE RapidJSON) + target_link_libraries(${ARROW_INTEGRATION_TARGET} PRIVATE RapidJSON + simdjson::simdjson) endforeach() else() set(ARROW_INTEGRATION_TARGET_SHARED) diff --git a/cpp/src/arrow/integration/CMakeLists.txt b/cpp/src/arrow/integration/CMakeLists.txt index 267d0adf11b..8374d6de10e 100644 --- a/cpp/src/arrow/integration/CMakeLists.txt +++ b/cpp/src/arrow/integration/CMakeLists.txt @@ -21,12 +21,17 @@ arrow_install_all_headers("arrow/integration") # - an executable that can be called to answer integration test requests # - a self-(unit)test for the C++ side of integration testing if(ARROW_BUILD_TESTS) - add_arrow_test(json_integration_test EXTRA_LINK_LIBS RapidJSON ${GFLAGS_LIBRARIES}) + add_arrow_test(json_integration_test + EXTRA_LINK_LIBS + RapidJSON + simdjson::simdjson + ${GFLAGS_LIBRARIES}) add_dependencies(arrow-integration arrow-json-integration-test) elseif(ARROW_BUILD_INTEGRATION) add_executable(arrow-json-integration-test json_integration_test.cc) target_link_libraries(arrow-json-integration-test RapidJSON + simdjson::simdjson ${ARROW_TEST_LINK_LIBS} ${GFLAGS_LIBRARIES} ${ARROW_GTEST_GTEST}) diff --git a/cpp/src/arrow/integration/json_integration.cc b/cpp/src/arrow/integration/json_integration.cc index f1844bcbda1..1ec20b4319d 100644 --- a/cpp/src/arrow/integration/json_integration.cc +++ b/cpp/src/arrow/integration/json_integration.cc @@ -27,6 +27,7 @@ #include "arrow/integration/json_internal.h" #include "arrow/io/file.h" #include "arrow/ipc/dictionary.h" +#include "arrow/json/json_writer.h" #include "arrow/record_batch.h" #include "arrow/result.h" #include "arrow/status.h" @@ -36,6 +37,8 @@ using arrow::ipc::DictionaryFieldMapper; using arrow::ipc::DictionaryMemo; +using JsonWriter = arrow::json::JsonWriter; + namespace arrow::internal::integration { // ---------------------------------------------------------------------- @@ -44,13 +47,10 @@ namespace arrow::internal::integration { class IntegrationJsonWriter::Impl { public: explicit Impl(const std::shared_ptr& schema) - : schema_(schema), mapper_(*schema), first_batch_written_(false) { - writer_.reset(new RjWriter(string_buffer_)); - } - + : schema_(schema), mapper_(*schema), first_batch_written_(false) {} Status Start() { - writer_->StartObject(); - RETURN_NOT_OK(json::WriteSchema(*schema_, mapper_, writer_.get())); + writer_.StartObject(); + RETURN_NOT_OK(json::WriteSchema(*schema_, mapper_, &writer_)); return Status::OK(); } @@ -59,26 +59,26 @@ class IntegrationJsonWriter::Impl { // Write dictionaries, if any if (!dictionaries.empty()) { - writer_->Key("dictionaries"); - writer_->StartArray(); + writer_.Key("dictionaries"); + writer_.StartArray(); for (const auto& entry : dictionaries) { - RETURN_NOT_OK(json::WriteDictionary(entry.first, entry.second, writer_.get())); + RETURN_NOT_OK(json::WriteDictionary(entry.first, entry.second, &writer_)); } - writer_->EndArray(); + writer_.EndArray(); } // Record batches - writer_->Key("batches"); - writer_->StartArray(); + writer_.Key("batches"); + writer_.StartArray(); first_batch_written_ = true; return Status::OK(); } Result Finish() { - writer_->EndArray(); // Record batches - writer_->EndObject(); + writer_.EndArray(); // Record batches + writer_.EndObject(); - return string_buffer_.GetString(); + return std::string(writer_.GetString()); } Status WriteRecordBatch(const RecordBatch& batch) { @@ -87,7 +87,7 @@ class IntegrationJsonWriter::Impl { if (!first_batch_written_) { RETURN_NOT_OK(FirstRecordBatch(batch)); } - return json::WriteRecordBatch(batch, writer_.get()); + return json::WriteRecordBatch(batch, &writer_); } private: @@ -96,8 +96,7 @@ class IntegrationJsonWriter::Impl { bool first_batch_written_; - rj::StringBuffer string_buffer_; - std::unique_ptr writer_; + JsonWriter writer_; }; IntegrationJsonWriter::IntegrationJsonWriter(const std::shared_ptr& schema) { diff --git a/cpp/src/arrow/integration/json_integration_test.cc b/cpp/src/arrow/integration/json_integration_test.cc index 23105e8ca17..c3c23ba7dd1 100644 --- a/cpp/src/arrow/integration/json_integration_test.cc +++ b/cpp/src/arrow/integration/json_integration_test.cc @@ -38,6 +38,7 @@ #include "arrow/ipc/reader.h" #include "arrow/ipc/test_common.h" #include "arrow/ipc/writer.h" +#include "arrow/json/json_writer.h" #include "arrow/pretty_print.h" #include "arrow/status.h" #include "arrow/testing/builder.h" @@ -723,8 +724,7 @@ static const char* json_example6 = R"example( )example"; void TestSchemaRoundTrip(const std::shared_ptr& schema) { - rj::StringBuffer sb; - rj::Writer writer(sb); + arrow::json::JsonWriter writer; DictionaryFieldMapper mapper(*schema); @@ -732,7 +732,7 @@ void TestSchemaRoundTrip(const std::shared_ptr& schema) { ASSERT_OK(json::WriteSchema(*schema, mapper, &writer)); writer.EndObject(); - std::string json_schema = sb.GetString(); + std::string json_schema(writer.GetString()); rj::Document d; // Pass explicit size to avoid ASAN issues with @@ -748,12 +748,11 @@ void TestSchemaRoundTrip(const std::shared_ptr& schema) { void TestArrayRoundTrip(const Array& array) { static std::string name = "dummy"; - rj::StringBuffer sb; - rj::Writer writer(sb); + arrow::json::JsonWriter writer; ASSERT_OK(json::WriteArray(name, array, &writer)); - std::string array_as_json = sb.GetString(); + std::string array_as_json(writer.GetString()); rj::Document d; // Pass explicit size to avoid ASAN issues with @@ -768,7 +767,6 @@ void TestArrayRoundTrip(const Array& array) { json::ReadArray(default_memory_pool(), d, ::arrow::field(name, array.type()))); ASSERT_OK(result_array->ValidateFull()); - // std::cout << array_as_json << std::endl; CompareArraysDetailed(0, *result_array, array); } diff --git a/cpp/src/arrow/integration/json_internal.cc b/cpp/src/arrow/integration/json_internal.cc index 1e57be51b06..ea8cd1ac3cd 100644 --- a/cpp/src/arrow/integration/json_internal.cc +++ b/cpp/src/arrow/integration/json_internal.cc @@ -36,6 +36,7 @@ #include "arrow/array/builder_time.h" #include "arrow/extension_type.h" #include "arrow/ipc/dictionary.h" +#include "arrow/json/json_writer.h" #include "arrow/record_batch.h" #include "arrow/result.h" #include "arrow/scalar.h" @@ -64,6 +65,8 @@ using arrow::ipc::DictionaryFieldMapper; using arrow::ipc::DictionaryMemo; using arrow::ipc::internal::FieldPosition; +using JsonWriter = arrow::json::JsonWriter; + namespace arrow::internal::integration::json { namespace { @@ -118,7 +121,7 @@ Result GetStringView(const rj::Value& str) { class SchemaWriter { public: explicit SchemaWriter(const Schema& schema, const DictionaryFieldMapper& mapper, - RjWriter* writer) + JsonWriter* writer) : schema_(schema), mapper_(mapper), writer_(writer) {} Status Write() { @@ -460,7 +463,7 @@ class SchemaWriter { private: const Schema& schema_; const DictionaryFieldMapper& mapper_; - RjWriter* writer_; + JsonWriter* writer_; }; Status SchemaWriter::VisitType(const DataType& type) { @@ -469,7 +472,7 @@ Status SchemaWriter::VisitType(const DataType& type) { class ArrayWriter { public: - ArrayWriter(const std::string& name, const Array& array, RjWriter* writer) + ArrayWriter(const std::string& name, const Array& array, JsonWriter* writer) : name_(name), array_(array), writer_(writer) {} Status Write() { return VisitArray(name_, array_); } @@ -491,9 +494,7 @@ class ArrayWriter { } void WriteRawNumber(std::string_view v) { - // Avoid RawNumber() as it misleadingly adds quotes - // (see https://github.com/Tencent/rapidjson/pull/1155) - writer_->RawValue(v.data(), v.size(), rj::kNumberType); + writer_->RawValue(v); } template String(value.ToIntegerString()); } else { - writer_->String(null_string, sizeof(null_string)); + writer_->String(std::string_view(null_string)); } } } @@ -610,7 +611,7 @@ class ArrayWriter { const Decimal64 value(arr.GetValue(i)); writer_->String(value.ToIntegerString()); } else { - writer_->String(null_string, sizeof(null_string)); + writer_->String(std::string_view(null_string)); } } } @@ -622,7 +623,7 @@ class ArrayWriter { const Decimal128 value(arr.GetValue(i)); writer_->String(value.ToIntegerString()); } else { - writer_->String(null_string, sizeof(null_string)); + writer_->String(std::string_view(null_string)); } } } @@ -634,7 +635,7 @@ class ArrayWriter { const Decimal256 value(arr.GetValue(i)); writer_->String(value.ToIntegerString()); } else { - writer_->String(null_string, sizeof(null_string)); + writer_->String(std::string_view(null_string)); } } } @@ -863,7 +864,7 @@ class ArrayWriter { private: const std::string& name_; const Array& array_; - RjWriter* writer_; + JsonWriter* writer_; }; Result GetUnitFromString(const std::string& unit_str) { @@ -2035,13 +2036,13 @@ Result> ReadRecordBatch( } Status WriteSchema(const Schema& schema, const DictionaryFieldMapper& mapper, - RjWriter* json_writer) { + JsonWriter* json_writer) { SchemaWriter converter(schema, mapper, json_writer); return converter.Write(); } Status WriteDictionary(int64_t id, const std::shared_ptr& dictionary, - RjWriter* writer) { + JsonWriter* writer) { writer->StartObject(); writer->Key("id"); writer->Int(static_cast(id)); @@ -2055,7 +2056,7 @@ Status WriteDictionary(int64_t id, const std::shared_ptr& dictionary, return Status::OK(); } -Status WriteRecordBatch(const RecordBatch& batch, RjWriter* writer) { +Status WriteRecordBatch(const RecordBatch& batch, JsonWriter* writer) { writer->StartObject(); writer->Key("count"); writer->Int(static_cast(batch.num_rows())); @@ -2076,7 +2077,7 @@ Status WriteRecordBatch(const RecordBatch& batch, RjWriter* writer) { return Status::OK(); } -Status WriteArray(const std::string& name, const Array& array, RjWriter* json_writer) { +Status WriteArray(const std::string& name, const Array& array, JsonWriter* json_writer) { ArrayWriter converter(name, array, json_writer); return converter.Write(); } diff --git a/cpp/src/arrow/integration/json_internal.h b/cpp/src/arrow/integration/json_internal.h index ac5bd2069d3..b7204f152a1 100644 --- a/cpp/src/arrow/integration/json_internal.h +++ b/cpp/src/arrow/integration/json_internal.h @@ -36,9 +36,14 @@ #include "arrow/util/visibility.h" namespace rj = arrow::rapidjson; -using RjWriter = rj::Writer; -using RjArray = rj::Value::ConstArray; using RjObject = rj::Value::ConstObject; +using RjArray = rj::Value::ConstArray; + +namespace arrow { +namespace json { +class JsonWriter; +} // namespace json +} // namespace arrow #define RETURN_NOT_FOUND(TOK, NAME, PARENT) \ if (NAME == (PARENT).MemberEnd()) { \ @@ -80,17 +85,17 @@ namespace arrow::internal::integration::json { /// \brief Append integration test Schema format to rapidjson writer ARROW_EXPORT Status WriteSchema(const Schema& schema, const ipc::DictionaryFieldMapper& mapper, - RjWriter* writer); + arrow::json::JsonWriter*); ARROW_EXPORT Status WriteDictionary(int64_t id, const std::shared_ptr& dictionary, - RjWriter* writer); + arrow::json::JsonWriter*); ARROW_EXPORT -Status WriteRecordBatch(const RecordBatch& batch, RjWriter* writer); +Status WriteRecordBatch(const RecordBatch& batch, arrow::json::JsonWriter*); ARROW_EXPORT -Status WriteArray(const std::string& name, const Array& array, RjWriter* writer); +Status WriteArray(const std::string& name, const Array& array, arrow::json::JsonWriter*); ARROW_EXPORT Result> ReadSchema(const rj::Value& json_obj, MemoryPool* pool, diff --git a/cpp/src/arrow/json/json_writer.cc b/cpp/src/arrow/json/json_writer.cc index 7b7a5b22c6c..37d401707ca 100644 --- a/cpp/src/arrow/json/json_writer.cc +++ b/cpp/src/arrow/json/json_writer.cc @@ -106,9 +106,7 @@ void JsonWriter::Null() { needs_comma_ = true; } -std::string_view JsonWriter::GetString() const { - return builder_.view().value(); -} +std::string_view JsonWriter::GetString() const { return builder_.view().value(); } void JsonWriter::Clear() { builder_.clear(); diff --git a/cpp/src/arrow/json/json_writer_test.cc b/cpp/src/arrow/json/json_writer_test.cc index 12ff2aac42c..5799fbea1b0 100644 --- a/cpp/src/arrow/json/json_writer_test.cc +++ b/cpp/src/arrow/json/json_writer_test.cc @@ -59,8 +59,7 @@ TEST(JsonWriter, NestedObject) { writer.EndObject(); - EXPECT_EQ(writer.GetString(), - R"({"child":{"x":true}})"); + EXPECT_EQ(writer.GetString(), R"({"child":{"x":true}})"); } TEST(JsonWriter, NullValue) { @@ -71,8 +70,7 @@ TEST(JsonWriter, NullValue) { writer.Null(); writer.EndObject(); - EXPECT_EQ(writer.GetString(), - R"({"value":null})"); + EXPECT_EQ(writer.GetString(), R"({"value":null})"); } TEST(JsonWriter, DoubleValue) { @@ -83,8 +81,7 @@ TEST(JsonWriter, DoubleValue) { writer.Double(3.14); writer.EndObject(); - EXPECT_EQ(writer.GetString(), - R"({"pi":3.14})"); + EXPECT_EQ(writer.GetString(), R"({"pi":3.14})"); } TEST(JsonWriter, UnsignedValues) { @@ -97,8 +94,7 @@ TEST(JsonWriter, UnsignedValues) { writer.Uint64(1234567890123ULL); writer.EndObject(); - EXPECT_EQ(writer.GetString(), - R"({"u32":42,"u64":1234567890123})"); + EXPECT_EQ(writer.GetString(), R"({"u32":42,"u64":1234567890123})"); } TEST(JsonWriter, Int64Value) { @@ -109,8 +105,7 @@ TEST(JsonWriter, Int64Value) { writer.Int64(-1234567890123LL); writer.EndObject(); - EXPECT_EQ(writer.GetString(), - R"({"i64":-1234567890123})"); + EXPECT_EQ(writer.GetString(), R"({"i64":-1234567890123})"); } TEST(JsonWriter, Clear) { From 106ee16e3f3f04566cad5ff5566ab45d89ecc7bf Mon Sep 17 00:00:00 2001 From: Aaditya Srinivasan Date: Tue, 21 Jul 2026 19:58:54 +0530 Subject: [PATCH 3/3] Make JsonWriter internal and update Meson --- cpp/src/arrow/CMakeLists.txt | 2 +- cpp/src/arrow/integration/json_integration.cc | 2 +- .../arrow/integration/json_integration_test.cc | 2 +- cpp/src/arrow/integration/json_internal.cc | 18 ++++++++---------- cpp/src/arrow/integration/json_internal.h | 6 ++---- cpp/src/arrow/integration/meson.build | 7 ++++++- cpp/src/arrow/json/CMakeLists.txt | 2 +- ...{json_writer.cc => json_writer_internal.cc} | 6 +----- .../{json_writer.h => json_writer_internal.h} | 1 - ...er_test.cc => json_writer_internal_test.cc} | 4 ++-- cpp/src/arrow/json/meson.build | 1 + cpp/src/arrow/meson.build | 1 + 12 files changed, 25 insertions(+), 27 deletions(-) rename cpp/src/arrow/json/{json_writer.cc => json_writer_internal.cc} (94%) rename cpp/src/arrow/json/{json_writer.h => json_writer_internal.h} (97%) rename cpp/src/arrow/json/{json_writer_test.cc => json_writer_internal_test.cc} (97%) diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index 406247213ca..59a8b128bdc 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -1042,7 +1042,7 @@ if(ARROW_JSON) json/from_string.cc json/object_parser.cc json/object_writer.cc - json/json_writer.cc + json/json_writer_internal.cc json/parser.cc json/reader.cc) foreach(ARROW_JSON_TARGET ${ARROW_JSON_TARGETS}) diff --git a/cpp/src/arrow/integration/json_integration.cc b/cpp/src/arrow/integration/json_integration.cc index 1ec20b4319d..d7c0fa59e4e 100644 --- a/cpp/src/arrow/integration/json_integration.cc +++ b/cpp/src/arrow/integration/json_integration.cc @@ -27,7 +27,7 @@ #include "arrow/integration/json_internal.h" #include "arrow/io/file.h" #include "arrow/ipc/dictionary.h" -#include "arrow/json/json_writer.h" +#include "arrow/json/json_writer_internal.h" #include "arrow/record_batch.h" #include "arrow/result.h" #include "arrow/status.h" diff --git a/cpp/src/arrow/integration/json_integration_test.cc b/cpp/src/arrow/integration/json_integration_test.cc index c3c23ba7dd1..e7a55d13e0d 100644 --- a/cpp/src/arrow/integration/json_integration_test.cc +++ b/cpp/src/arrow/integration/json_integration_test.cc @@ -38,7 +38,7 @@ #include "arrow/ipc/reader.h" #include "arrow/ipc/test_common.h" #include "arrow/ipc/writer.h" -#include "arrow/json/json_writer.h" +#include "arrow/json/json_writer_internal.h" #include "arrow/pretty_print.h" #include "arrow/status.h" #include "arrow/testing/builder.h" diff --git a/cpp/src/arrow/integration/json_internal.cc b/cpp/src/arrow/integration/json_internal.cc index ea8cd1ac3cd..d0baad67e1e 100644 --- a/cpp/src/arrow/integration/json_internal.cc +++ b/cpp/src/arrow/integration/json_internal.cc @@ -36,7 +36,7 @@ #include "arrow/array/builder_time.h" #include "arrow/extension_type.h" #include "arrow/ipc/dictionary.h" -#include "arrow/json/json_writer.h" +#include "arrow/json/json_writer_internal.h" #include "arrow/record_batch.h" #include "arrow/result.h" #include "arrow/scalar.h" @@ -493,9 +493,7 @@ class ArrayWriter { return Status::OK(); } - void WriteRawNumber(std::string_view v) { - writer_->RawValue(v); - } + void WriteRawNumber(std::string_view v) { writer_->RawValue(v); } template @@ -523,11 +521,10 @@ class ArrayWriter { for (int64_t i = 0; i < arr.length(); ++i) { if (arr.IsValid(i)) { fmt(arr.Value(i), [&](std::string_view repr) { - writer_->String(repr.data(), static_cast(repr.size())); + writer_->String(std::string_view(repr.data(), repr.size())); }); } else { - writer_->String(null_string.data(), - static_cast(null_string.size())); + writer_->String(std::string_view(null_string.data(), null_string.size())); } } } @@ -554,7 +551,7 @@ class ArrayWriter { if constexpr (Type::is_utf8) { // UTF8 string, write as is auto view = arr.GetView(i); - writer_->String(view.data(), static_cast(view.size())); + writer_->String(std::string_view(view.data(), view.size())); } else { // Binary, encode to hexadecimal. writer_->String(HexEncode(arr.GetView(i))); @@ -671,7 +668,7 @@ class ArrayWriter { // them exactly. ::arrow::internal::StringFormatter::ArrowType> formatter; auto append = [this](std::string_view v) { - writer_->String(v.data(), static_cast(v.size())); + writer_->String(std::string_view(v.data(), v.size())); return Status::OK(); }; for (int i = 0; i < length; ++i) { @@ -693,7 +690,8 @@ class ArrayWriter { if (s.is_inline()) { writer_->Key("INLINED"); if constexpr (ArrayType::TypeClass::is_utf8) { - writer_->String(reinterpret_cast(s.inline_data()), s.size()); + writer_->String( + std::string_view(reinterpret_cast(s.inline_data()), s.size())); } else { writer_->String(HexEncode(s.inline_data(), s.size())); } diff --git a/cpp/src/arrow/integration/json_internal.h b/cpp/src/arrow/integration/json_internal.h index b7204f152a1..d8a56ceb0e5 100644 --- a/cpp/src/arrow/integration/json_internal.h +++ b/cpp/src/arrow/integration/json_internal.h @@ -39,11 +39,9 @@ namespace rj = arrow::rapidjson; using RjObject = rj::Value::ConstObject; using RjArray = rj::Value::ConstArray; -namespace arrow { -namespace json { +namespace arrow::json { class JsonWriter; -} // namespace json -} // namespace arrow +} // namespace arrow::json #define RETURN_NOT_FOUND(TOK, NAME, PARENT) \ if (NAME == (PARENT).MemberEnd()) { \ diff --git a/cpp/src/arrow/integration/meson.build b/cpp/src/arrow/integration/meson.build index 6437c380bb3..17b6ea74495 100644 --- a/cpp/src/arrow/integration/meson.build +++ b/cpp/src/arrow/integration/meson.build @@ -20,7 +20,12 @@ install_headers(['json_integration.h']) exc = executable( 'arrow-json-integration-test', sources: ['json_integration_test.cc'], - dependencies: [arrow_test_dep_no_main, rapidjson_dep, gflags_dep], + dependencies: [ + arrow_test_dep_no_main, + rapidjson_dep, + simdjson_dep, + gflags_dep, + ], ) arrow_c_data_integration_lib = library( diff --git a/cpp/src/arrow/json/CMakeLists.txt b/cpp/src/arrow/json/CMakeLists.txt index b0e95bfe040..5d03a059cba 100644 --- a/cpp/src/arrow/json/CMakeLists.txt +++ b/cpp/src/arrow/json/CMakeLists.txt @@ -24,7 +24,7 @@ add_arrow_test(test parser_test.cc reader_test.cc object_parser_test.cc - json_writer_test.cc + json_writer_internal_test.cc PREFIX "arrow-json" EXTRA_LINK_LIBS diff --git a/cpp/src/arrow/json/json_writer.cc b/cpp/src/arrow/json/json_writer_internal.cc similarity index 94% rename from cpp/src/arrow/json/json_writer.cc rename to cpp/src/arrow/json/json_writer_internal.cc index 37d401707ca..11fe70a7415 100644 --- a/cpp/src/arrow/json/json_writer.cc +++ b/cpp/src/arrow/json/json_writer_internal.cc @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -#include "arrow/json/json_writer.h" +#include "arrow/json/json_writer_internal.h" namespace arrow::json { @@ -54,10 +54,6 @@ void JsonWriter::String(std::string_view value) { needs_comma_ = true; } -void JsonWriter::String(const char* data, size_t length) { - String(std::string_view(data, length)); -} - void JsonWriter::RawValue(std::string_view value) { MaybeComma(); builder_.append_raw(value); diff --git a/cpp/src/arrow/json/json_writer.h b/cpp/src/arrow/json/json_writer_internal.h similarity index 97% rename from cpp/src/arrow/json/json_writer.h rename to cpp/src/arrow/json/json_writer_internal.h index f11516f0e69..e4d6e49885f 100644 --- a/cpp/src/arrow/json/json_writer.h +++ b/cpp/src/arrow/json/json_writer_internal.h @@ -39,7 +39,6 @@ class ARROW_EXPORT JsonWriter { void Key(std::string_view key); void String(std::string_view value); - void String(const char* data, size_t length); void RawValue(std::string_view value); void Bool(bool value); diff --git a/cpp/src/arrow/json/json_writer_test.cc b/cpp/src/arrow/json/json_writer_internal_test.cc similarity index 97% rename from cpp/src/arrow/json/json_writer_test.cc rename to cpp/src/arrow/json/json_writer_internal_test.cc index 5799fbea1b0..01e6d7ecb8c 100644 --- a/cpp/src/arrow/json/json_writer_test.cc +++ b/cpp/src/arrow/json/json_writer_internal_test.cc @@ -17,7 +17,7 @@ #include -#include "arrow/json/json_writer.h" +#include "arrow/json/json_writer_internal.h" namespace arrow::json { @@ -143,7 +143,7 @@ TEST(JsonWriter, StringWithExplicitLength) { writer.StartObject(); writer.Key("value"); - writer.String(value, 3); + writer.String(std::string_view(value, 3)); writer.EndObject(); ASSERT_EQ(writer.GetString(), R"({"value":"abc"})"); diff --git a/cpp/src/arrow/json/meson.build b/cpp/src/arrow/json/meson.build index bc1567df9ec..1a7d09fbf83 100644 --- a/cpp/src/arrow/json/meson.build +++ b/cpp/src/arrow/json/meson.build @@ -22,6 +22,7 @@ exc = executable( 'chunker_test.cc', 'converter_test.cc', 'from_string_test.cc', + 'json_writer_internal_test.cc', 'parser_test.cc', 'reader_test.cc', ], diff --git a/cpp/src/arrow/meson.build b/cpp/src/arrow/meson.build index 831bc121808..c07cd3a6165 100644 --- a/cpp/src/arrow/meson.build +++ b/cpp/src/arrow/meson.build @@ -517,6 +517,7 @@ if needs_json 'json/from_string.cc', 'json/object_parser.cc', 'json/object_writer.cc', + 'json/json_writer_internal.cc', 'json/parser.cc', 'json/reader.cc', ],