diff --git a/ci/docker/ubuntu-24.04-cpp.dockerfile b/ci/docker/ubuntu-24.04-cpp.dockerfile index ce2a30d980ab..00cc8796c546 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 e792574a1837..59a8b128bdcc 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) @@ -1041,6 +1042,7 @@ if(ARROW_JSON) json/from_string.cc json/object_parser.cc json/object_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/CMakeLists.txt b/cpp/src/arrow/integration/CMakeLists.txt index 267d0adf11bf..8374d6de10e4 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 f1844bcbda13..d7c0fa59e4e2 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_internal.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 23105e8ca172..e7a55d13e0d2 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_internal.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 1e57be51b060..d0baad67e1e5 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_internal.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_); } @@ -490,11 +493,7 @@ class ArrayWriter { return Status::OK(); } - 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); - } + void WriteRawNumber(std::string_view v) { writer_->RawValue(v); } template @@ -522,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())); } } } @@ -553,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))); @@ -598,7 +596,7 @@ class ArrayWriter { const Decimal32 value(arr.GetValue(i)); writer_->String(value.ToIntegerString()); } else { - writer_->String(null_string, sizeof(null_string)); + writer_->String(std::string_view(null_string)); } } } @@ -610,7 +608,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 +620,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 +632,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)); } } } @@ -670,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) { @@ -692,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())); } @@ -863,7 +862,7 @@ class ArrayWriter { private: const std::string& name_; const Array& array_; - RjWriter* writer_; + JsonWriter* writer_; }; Result GetUnitFromString(const std::string& unit_str) { @@ -2035,13 +2034,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 +2054,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 +2075,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 ac5bd2069d39..d8a56ceb0e57 100644 --- a/cpp/src/arrow/integration/json_internal.h +++ b/cpp/src/arrow/integration/json_internal.h @@ -36,9 +36,12 @@ #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::json { +class JsonWriter; +} // namespace arrow::json #define RETURN_NOT_FOUND(TOK, NAME, PARENT) \ if (NAME == (PARENT).MemberEnd()) { \ @@ -80,17 +83,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/integration/meson.build b/cpp/src/arrow/integration/meson.build index 6437c380bb31..17b6ea744958 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 b5b47c0ed37b..5d03a059cba6 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_internal_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_internal.cc b/cpp/src/arrow/json/json_writer_internal.cc new file mode 100644 index 000000000000..11fe70a7415e --- /dev/null +++ b/cpp/src/arrow/json/json_writer_internal.cc @@ -0,0 +1,118 @@ +// 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_internal.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::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_internal.h b/cpp/src/arrow/json/json_writer_internal.h new file mode 100644 index 000000000000..e4d6e49885f8 --- /dev/null +++ b/cpp/src/arrow/json/json_writer_internal.h @@ -0,0 +1,66 @@ +// 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 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_internal_test.cc b/cpp/src/arrow/json/json_writer_internal_test.cc new file mode 100644 index 000000000000..01e6d7ecb8c1 --- /dev/null +++ b/cpp/src/arrow/json/json_writer_internal_test.cc @@ -0,0 +1,152 @@ +// 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_internal.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(std::string_view(value, 3)); + writer.EndObject(); + + ASSERT_EQ(writer.GetString(), R"({"value":"abc"})"); +} + +} // namespace arrow::json diff --git a/cpp/src/arrow/json/meson.build b/cpp/src/arrow/json/meson.build index bc1567df9ec0..1a7d09fbf83e 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 831bc1218083..c07cd3a61651 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', ],