This is an automated email from the ASF dual-hosted git repository.

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 06e3b9a864 GH-50859: [C++][Parquet] Move JsonWriter to simdjson 
utilities (#50990)
06e3b9a864 is described below

commit 06e3b9a864a78c973c1436271ecf7bc897deecd0
Author: Rok Mihevc <[email protected]>
AuthorDate: Mon Aug 31 17:36:38 2026 +0200

    GH-50859: [C++][Parquet] Move JsonWriter to simdjson utilities (#50990)
    
    ### Rationale for this change
    
    Parquet uses JsonWriter when ARROW_JSON=OFF, but its implementation was 
only built with Arrow JSON, causing link failures.
    
    ### What changes are included in this PR?
    
    Move JsonWriter to the simdjson utilities and update its callers and 
CMake/Meson builds.
    
    ### Are these changes tested?
    
    Yes. CMake shared/static and Meson Parquet builds pass with JSON disabled. 
Unit tests and pre-commit checks also pass.
    
    ### Are there any user-facing changes?
    
     No. This only fixes the affected build configuration.
    
    AI disclosure - this was AI generated to test alternative approach to 
#50900.
    * GitHub Issue: #50859
    
    Authored-by: Rok Mihevc <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/cmake_modules/DefineOptions.cmake              |   3 +-
 cpp/cmake_modules/ThirdpartyToolchain.cmake        |   5 +-
 cpp/meson.build                                    |   1 +
 cpp/src/arrow/CMakeLists.txt                       |  11 +-
 cpp/src/arrow/dataset/file_json_test.cc            |   8 +-
 cpp/src/arrow/extension/fixed_shape_tensor.cc      |   3 +-
 cpp/src/arrow/extension/opaque.cc                  |   3 +-
 cpp/src/arrow/extension/variable_shape_tensor.cc   |   3 +-
 .../flight/sql/odbc/odbc_impl/json_converter.cc    |   4 +-
 cpp/src/arrow/integration/json_integration.cc      |   3 +-
 cpp/src/arrow/integration/json_integration_test.cc |   5 +-
 cpp/src/arrow/integration/json_internal.cc         |   3 +-
 cpp/src/arrow/integration/json_internal.h          |  13 +-
 cpp/src/arrow/json/CMakeLists.txt                  |   2 -
 cpp/src/arrow/json/json_writer_internal.cc         | 232 --------
 cpp/src/arrow/json/json_writer_internal.h          |  77 ---
 cpp/src/arrow/json/meson.build                     |   2 -
 cpp/src/arrow/json/object_parser.cc                | 178 -------
 cpp/src/arrow/json/object_parser.h                 |  54 --
 cpp/src/arrow/json/object_parser_test.cc           | 113 ----
 cpp/src/arrow/meson.build                          |  39 +-
 cpp/src/arrow/util/CMakeLists.txt                  |   8 +
 cpp/src/arrow/util/meson.build                     |   8 +-
 cpp/src/arrow/util/simdjson_internal.cc            | 584 +++++++++++++++++++++
 cpp/src/arrow/util/simdjson_internal.h             | 288 ++++------
 .../simdjson_internal_test.cc}                     |  89 +++-
 .../encryption/file_system_key_material_store.cc   |   7 +-
 cpp/src/parquet/encryption/key_material.cc         |  32 +-
 cpp/src/parquet/encryption/key_material.h          |  11 -
 cpp/src/parquet/encryption/key_metadata.cc         |  12 +-
 .../parquet/encryption/local_wrap_kms_client.cc    |   9 +-
 cpp/src/parquet/geospatial/util_json_internal.cc   |   3 +-
 cpp/src/parquet/printer.cc                         |   4 +-
 cpp/src/parquet/types.cc                           |  18 +-
 34 files changed, 869 insertions(+), 966 deletions(-)

diff --git a/cpp/cmake_modules/DefineOptions.cmake 
b/cpp/cmake_modules/DefineOptions.cmake
index bfe4485aa9..1d12edd061 100644
--- a/cpp/cmake_modules/DefineOptions.cmake
+++ b/cpp/cmake_modules/DefineOptions.cmake
@@ -599,8 +599,7 @@ takes precedence over ccache if a storage backend is 
configured" ON)
                 "Build support for encryption. Fail if OpenSSL is not found"
                 OFF
                 DEPENDS
-                ARROW_FILESYSTEM
-                ARROW_JSON)
+                ARROW_FILESYSTEM)
 
   #----------------------------------------------------------------------
   set_option_category("Gandiva")
diff --git a/cpp/cmake_modules/ThirdpartyToolchain.cmake 
b/cpp/cmake_modules/ThirdpartyToolchain.cmake
index 8888f52f72..0fc8f23435 100644
--- a/cpp/cmake_modules/ThirdpartyToolchain.cmake
+++ b/cpp/cmake_modules/ThirdpartyToolchain.cmake
@@ -383,7 +383,6 @@ if(ARROW_WITH_OPENTELEMETRY)
 endif()
 
 if(ARROW_PARQUET)
-  set(ARROW_WITH_RAPIDJSON ON)
   set(ARROW_WITH_SIMDJSON ON)
   set(ARROW_WITH_THRIFT ON)
 endif()
@@ -411,11 +410,11 @@ if(ARROW_AZURE)
   set(ARROW_WITH_AZURE_SDK ON)
 endif()
 
-if(ARROW_JSON OR ARROW_FLIGHT_SQL_ODBC)
+if(ARROW_JSON)
   set(ARROW_WITH_RAPIDJSON ON)
 endif()
 
-if(ARROW_JSON)
+if(ARROW_JSON OR ARROW_FLIGHT_SQL_ODBC)
   set(ARROW_WITH_SIMDJSON ON)
 endif()
 
diff --git a/cpp/meson.build b/cpp/meson.build
index 023877e19d..5532d866db 100644
--- a/cpp/meson.build
+++ b/cpp/meson.build
@@ -104,6 +104,7 @@ needs_testing = (get_option('testing').enabled()
     or needs_integration
 )
 needs_json = get_option('json').enabled() or needs_testing
+needs_simdjson = needs_json or needs_parquet
 needs_brotli = get_option('brotli').enabled() or needs_fuzzing
 needs_bz2 = get_option('bz2').enabled()
 needs_lz4 = get_option('lz4').enabled()
diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt
index b9ff2ffa2c..d7086773a1 100644
--- a/cpp/src/arrow/CMakeLists.txt
+++ b/cpp/src/arrow/CMakeLists.txt
@@ -644,6 +644,9 @@ endif()
 if(ARROW_WITH_OPENTELEMETRY)
   list(APPEND ARROW_UTIL_SRCS util/tracing_internal.cc)
 endif()
+if(ARROW_WITH_SIMDJSON)
+  list(APPEND ARROW_UTIL_SRCS util/simdjson_internal.cc)
+endif()
 if(ARROW_WITH_SNAPPY)
   list(APPEND ARROW_UTIL_SRCS util/compression_snappy.cc)
 endif()
@@ -664,6 +667,12 @@ foreach(ARROW_UTIL_TARGET ${ARROW_UTIL_TARGETS})
   target_link_libraries(${ARROW_UTIL_TARGET} PRIVATE ${ARROW_XSIMD})
 endforeach()
 
+if(ARROW_WITH_SIMDJSON)
+  foreach(ARROW_UTIL_TARGET ${ARROW_UTIL_TARGETS})
+    target_link_libraries(${ARROW_UTIL_TARGET} PRIVATE arrow::simdjson)
+  endforeach()
+endif()
+
 if(ARROW_USE_BOOST)
   foreach(ARROW_UTIL_TARGET ${ARROW_UTIL_TARGETS})
     target_link_libraries(${ARROW_UTIL_TARGET} PRIVATE Boost::headers)
@@ -1088,8 +1097,6 @@ if(ARROW_JSON)
                            json/chunker.cc
                            json/converter.cc
                            json/from_string.cc
-                           json/json_writer_internal.cc
-                           json/object_parser.cc
                            json/parser.cc
                            json/reader.cc)
   foreach(ARROW_JSON_TARGET ${ARROW_JSON_TARGETS})
diff --git a/cpp/src/arrow/dataset/file_json_test.cc 
b/cpp/src/arrow/dataset/file_json_test.cc
index b96d4ffed9..aa060e73bb 100644
--- a/cpp/src/arrow/dataset/file_json_test.cc
+++ b/cpp/src/arrow/dataset/file_json_test.cc
@@ -20,11 +20,11 @@
 #include "arrow/dataset/plan.h"
 #include "arrow/dataset/test_util_internal.h"
 #include "arrow/filesystem/mockfs.h"
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/json/parser.h"
 #include "arrow/testing/gtest_util.h"
 #include "arrow/testing/util.h"
 #include "arrow/util/logging_internal.h"
+#include "arrow/util/simdjson_internal.h"
 
 namespace arrow {
 
@@ -104,11 +104,11 @@ struct WriteVisitor {
     return Status::OK();
   }
 
-  json::JsonWriter& writer_;
+  ::arrow::internal::JsonWriter& writer_;
   const Scalar& scalar_;
 };
 
-Status WriteJson(const StructScalar& scalar, json::JsonWriter* writer) {
+Status WriteJson(const StructScalar& scalar, ::arrow::internal::JsonWriter* 
writer) {
   WriteVisitor visitor{*writer, scalar};
   return VisitWriteableTypeId(Type::STRUCT, &visitor);
 }
@@ -122,7 +122,7 @@ class JsonFormatHelper {
     std::stringstream ss;
 
     for (const auto& scalar : scalars) {
-      json::JsonWriter writer;
+      ::arrow::internal::JsonWriter writer;
       RETURN_NOT_OK(WriteJson(*scalar, &writer));
 
       ARROW_ASSIGN_OR_RAISE(auto json, writer.GetString());
diff --git a/cpp/src/arrow/extension/fixed_shape_tensor.cc 
b/cpp/src/arrow/extension/fixed_shape_tensor.cc
index 6a86d6a7a6..69eed2eed0 100644
--- a/cpp/src/arrow/extension/fixed_shape_tensor.cc
+++ b/cpp/src/arrow/extension/fixed_shape_tensor.cc
@@ -27,7 +27,6 @@
 
 #include "arrow/array/array_nested.h"
 #include "arrow/array/array_primitive.h"
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/tensor.h"
 #include "arrow/util/logging_internal.h"
 #include "arrow/util/print_internal.h"
@@ -35,7 +34,7 @@
 #include "arrow/util/sort_internal.h"
 #include "arrow/util/string.h"
 
-using ::arrow::json::JsonWriter;
+using ::arrow::internal::JsonWriter;
 
 namespace arrow::extension {
 
diff --git a/cpp/src/arrow/extension/opaque.cc 
b/cpp/src/arrow/extension/opaque.cc
index c6068babdb..2dae9ef567 100644
--- a/cpp/src/arrow/extension/opaque.cc
+++ b/cpp/src/arrow/extension/opaque.cc
@@ -19,13 +19,12 @@
 
 #include <sstream>
 
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/util/logging_internal.h"
 #include "arrow/util/simdjson_internal.h"
 
 #include <simdjson.h>
 
-using ::arrow::json::JsonWriter;
+using ::arrow::internal::JsonWriter;
 
 namespace arrow::extension {
 
diff --git a/cpp/src/arrow/extension/variable_shape_tensor.cc 
b/cpp/src/arrow/extension/variable_shape_tensor.cc
index 784cd334b1..0e4f5f54ca 100644
--- a/cpp/src/arrow/extension/variable_shape_tensor.cc
+++ b/cpp/src/arrow/extension/variable_shape_tensor.cc
@@ -23,7 +23,6 @@
 #include "arrow/extension/variable_shape_tensor.h"
 
 #include "arrow/array/array_primitive.h"
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/scalar.h"
 #include "arrow/tensor.h"
 #include "arrow/util/logging_internal.h"
@@ -32,7 +31,7 @@
 #include "arrow/util/sort_internal.h"
 #include "arrow/util/string.h"
 
-using ::arrow::json::JsonWriter;
+using ::arrow::internal::JsonWriter;
 
 namespace arrow::extension {
 
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc
index 64e91ecd9b..ea2fad4257 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc
@@ -20,8 +20,8 @@
 #include <boost/beast/core/detail/base64.hpp>
 #include "arrow/builder.h"
 #include "arrow/flight/sql/odbc/odbc_impl/util.h"
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/scalar.h"
+#include "arrow/util/simdjson_internal.h"
 #include "arrow/visitor.h"
 
 using boost::beast::detail::base64::encode;
@@ -30,7 +30,7 @@ namespace base64 = boost::beast::detail::base64;
 
 namespace arrow::flight::sql::odbc {
 
-using ::arrow::json::JsonWriter;
+using ::arrow::internal::JsonWriter;
 using util::ThrowIfNotOK;
 
 namespace {
diff --git a/cpp/src/arrow/integration/json_integration.cc 
b/cpp/src/arrow/integration/json_integration.cc
index f978e1da54..98bb58f286 100644
--- a/cpp/src/arrow/integration/json_integration.cc
+++ b/cpp/src/arrow/integration/json_integration.cc
@@ -29,7 +29,6 @@
 #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"
@@ -40,7 +39,7 @@
 using arrow::ipc::DictionaryFieldMapper;
 using arrow::ipc::DictionaryMemo;
 
-using JsonWriter = arrow::json::JsonWriter;
+using JsonWriter = arrow::internal::JsonWriter;
 
 namespace arrow::internal::integration {
 
diff --git a/cpp/src/arrow/integration/json_integration_test.cc 
b/cpp/src/arrow/integration/json_integration_test.cc
index 700551c23b..98bbd3f56b 100644
--- a/cpp/src/arrow/integration/json_integration_test.cc
+++ b/cpp/src/arrow/integration/json_integration_test.cc
@@ -38,7 +38,6 @@
 #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"
@@ -725,7 +724,7 @@ static const char* json_example6 = R"example(
 )example";
 
 void TestSchemaRoundTrip(const std::shared_ptr<Schema>& schema) {
-  arrow::json::JsonWriter writer;
+  arrow::internal::JsonWriter writer;
 
   DictionaryFieldMapper mapper(*schema);
 
@@ -749,7 +748,7 @@ void TestSchemaRoundTrip(const std::shared_ptr<Schema>& 
schema) {
 void TestArrayRoundTrip(const Array& array) {
   static std::string name = "dummy";
 
-  arrow::json::JsonWriter writer;
+  arrow::internal::JsonWriter writer;
 
   ASSERT_OK(json::WriteArray(name, array, &writer));
 
diff --git a/cpp/src/arrow/integration/json_internal.cc 
b/cpp/src/arrow/integration/json_internal.cc
index abf48b9df7..0f72207bd4 100644
--- a/cpp/src/arrow/integration/json_internal.cc
+++ b/cpp/src/arrow/integration/json_internal.cc
@@ -36,7 +36,6 @@
 #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"
@@ -66,7 +65,7 @@ using arrow::ipc::DictionaryFieldMapper;
 using arrow::ipc::DictionaryMemo;
 using arrow::ipc::internal::FieldPosition;
 
-using JsonWriter = arrow::json::JsonWriter;
+using JsonWriter = arrow::internal::JsonWriter;
 
 namespace arrow::internal::integration::json {
 
diff --git a/cpp/src/arrow/integration/json_internal.h 
b/cpp/src/arrow/integration/json_internal.h
index b61d5b5746..c2d6fd3d45 100644
--- a/cpp/src/arrow/integration/json_internal.h
+++ b/cpp/src/arrow/integration/json_internal.h
@@ -32,26 +32,25 @@ using JsonValue = simdjson::dom::element;
 using JsonObject = simdjson::dom::object;
 using JsonArray = simdjson::dom::array;
 
-namespace arrow::json {
+namespace arrow::internal {
 class JsonWriter;
-}  // namespace arrow::json
+}  // namespace arrow::internal
 
 namespace arrow::internal::integration::json {
 
 /// \brief Append integration test Schema format to JSON writer
 ARROW_EXPORT
 Status WriteSchema(const Schema& schema, const ipc::DictionaryFieldMapper& 
mapper,
-                   arrow::json::JsonWriter*);
+                   JsonWriter*);
 
 ARROW_EXPORT
-Status WriteDictionary(int64_t id, const std::shared_ptr<Array>& dictionary,
-                       arrow::json::JsonWriter*);
+Status WriteDictionary(int64_t id, const std::shared_ptr<Array>& dictionary, 
JsonWriter*);
 
 ARROW_EXPORT
-Status WriteRecordBatch(const RecordBatch& batch, arrow::json::JsonWriter*);
+Status WriteRecordBatch(const RecordBatch& batch, JsonWriter*);
 
 ARROW_EXPORT
-Status WriteArray(const std::string& name, const Array& array, 
arrow::json::JsonWriter*);
+Status WriteArray(const std::string& name, const Array& array, JsonWriter*);
 
 ARROW_EXPORT
 Result<std::shared_ptr<Schema>> ReadSchema(const JsonValue& json_obj, 
MemoryPool* pool,
diff --git a/cpp/src/arrow/json/CMakeLists.txt 
b/cpp/src/arrow/json/CMakeLists.txt
index e7719c771c..b930034537 100644
--- a/cpp/src/arrow/json/CMakeLists.txt
+++ b/cpp/src/arrow/json/CMakeLists.txt
@@ -21,8 +21,6 @@ add_arrow_test(test
                chunker_test.cc
                converter_test.cc
                from_string_test.cc
-               json_writer_internal_test.cc
-               object_parser_test.cc
                parser_test.cc
                reader_test.cc
                PREFIX
diff --git a/cpp/src/arrow/json/json_writer_internal.cc 
b/cpp/src/arrow/json/json_writer_internal.cc
deleted file mode 100644
index e49841fda6..0000000000
--- a/cpp/src/arrow/json/json_writer_internal.cc
+++ /dev/null
@@ -1,232 +0,0 @@
-// 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"
-#include "arrow/util/simdjson_internal.h"
-
-namespace arrow::json {
-
-namespace sj = simdjson::ondemand;
-
-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;
-}
-
-Status JsonWriter::WriteValue(sj::value value) {
-  return internal::VisitJsonValue(
-      value,
-
-      [&](sj::object object) -> Status {
-        StartObject();
-
-        for (auto field : object) {
-          ARROW_ASSIGN_OR_RAISE(
-              auto key, internal::ResolveSimdjsonResult(field.unescaped_key(),
-                                                        "Failed to get object 
key"));
-
-          Key(key);
-
-          ARROW_ASSIGN_OR_RAISE(auto field_value,
-                                internal::ResolveSimdjsonResult(
-                                    field.value(), "Failed to get object 
value"));
-
-          RETURN_NOT_OK(WriteValue(field_value));
-        }
-
-        EndObject();
-        return Status::OK();
-      },
-
-      [&](sj::array array) -> Status {
-        StartArray();
-
-        for (auto element : array) {
-          ARROW_ASSIGN_OR_RAISE(
-              auto element_value,
-              internal::ResolveSimdjsonResult(element, "Failed to iterate JSON 
array"));
-
-          RETURN_NOT_OK(WriteValue(element_value));
-        }
-
-        EndArray();
-        return Status::OK();
-      },
-
-      [&](std::string_view string_value) -> Status {
-        String(string_value);
-        return Status::OK();
-      },
-
-      [&](bool bool_value) -> Status {
-        Bool(bool_value);
-        return Status::OK();
-      },
-
-      [&]() -> Status {
-        Null();
-        return Status::OK();
-      },
-
-      [&](int64_t value) -> Status {
-        Int64(value);
-        return Status::OK();
-      },
-
-      [&](uint64_t value) -> Status {
-        Uint64(value);
-        return Status::OK();
-      },
-
-      [&](double value) -> Status {
-        Double(value);
-        return Status::OK();
-      },
-
-      [&](sj::value value) -> Status {
-        ARROW_ASSIGN_OR_RAISE(auto raw_json, internal::ResolveSimdjsonResult(
-                                                 
simdjson::to_json_string(value),
-                                                 "Failed to get raw JSON"));
-        RawValue(raw_json);
-        return Status::OK();
-      });
-}
-
-void JsonWriter::Null() {
-  MaybeComma();
-  builder_.append_null();
-  needs_comma_ = true;
-}
-
-Result<std::string_view> JsonWriter::GetString() const {
-  std::string_view view;
-  if (auto error = builder_.view().get(view); error != simdjson::SUCCESS) {
-    if (error == simdjson::OUT_OF_CAPACITY) {
-      return Status::OutOfMemory(
-          "OutOfMemory when allocating buffer to serialize json to string");
-    }
-    return Status::Invalid("Failed to retrieve json from string builder: ",
-                           simdjson::error_message(error));
-  }
-  return view;
-}
-
-Result<std::string> JsonWriter::GetPrettyString(
-    const simdjson::fractured_json_options& options) const {
-  ARROW_ASSIGN_OR_RAISE(std::string_view json, GetString());
-  return simdjson::fractured_json_string(json, options);
-}
-
-void JsonWriter::Clear() {
-  builder_.clear();
-  needs_comma_ = false;
-}
-
-void JsonWriter::MaybeComma() {
-  if (needs_comma_) {
-    builder_.append_comma();
-  }
-}
-
-void JsonWriter::StringField(std::string_view key, std::string_view value) {
-  Key(key);
-  String(value);
-}
-
-void JsonWriter::BoolField(std::string_view key, bool value) {
-  Key(key);
-  Bool(value);
-}
-
-void JsonWriter::IntField(std::string_view key, int32_t value) {
-  Key(key);
-  Int(value);
-}
-
-}  // namespace arrow::json
diff --git a/cpp/src/arrow/json/json_writer_internal.h 
b/cpp/src/arrow/json/json_writer_internal.h
deleted file mode 100644
index e407695daa..0000000000
--- a/cpp/src/arrow/json/json_writer_internal.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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 <simdjson.h>
-
-#include <cstdint>
-#include <string_view>
-
-#include "arrow/result.h"
-#include "arrow/status.h"
-#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);
-
-  Status WriteValue(simdjson::ondemand::value value);
-
-  void Null();
-
-  void StringField(std::string_view key, std::string_view value);
-  void BoolField(std::string_view key, bool value);
-  void IntField(std::string_view key, int32_t value);
-
-  Result<std::string_view> GetString() const;
-
-  Result<std::string> GetPrettyString(
-      const simdjson::fractured_json_options& options = {}) 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/meson.build b/cpp/src/arrow/json/meson.build
index ee2a26b2cc..e5383b90b9 100644
--- a/cpp/src/arrow/json/meson.build
+++ b/cpp/src/arrow/json/meson.build
@@ -22,7 +22,6 @@ exc = executable(
         'chunker_test.cc',
         'converter_test.cc',
         'from_string_test.cc',
-        'json_writer_internal_test.cc',
         'parser_test.cc',
         'reader_test.cc',
     ],
@@ -44,7 +43,6 @@ install_headers(
         'chunker.h',
         'converter.h',
         'from_string.h',
-        'object_parser.h',
         'options.h',
         'parser.h',
         'rapidjson_defs.h',
diff --git a/cpp/src/arrow/json/object_parser.cc 
b/cpp/src/arrow/json/object_parser.cc
deleted file mode 100644
index 4aad2aca67..0000000000
--- a/cpp/src/arrow/json/object_parser.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-// 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/object_parser.h"
-
-#include <simdjson.h>
-
-namespace arrow {
-namespace json {
-namespace internal {
-
-class ObjectParser::Impl {
- public:
-  Status Parse(std::string_view json) {
-    // Copy into padded buffer
-    padded_json_ = simdjson::padded_string(json);
-
-    // Store parsed document
-    if (auto error = parser_.iterate(padded_json_).get(document_)) {
-      return Status::Invalid("JSON parse error: ", 
simdjson::error_message(error));
-    }
-
-    // Validate root is an object
-    auto object = document_.get_object();
-    if (object.error()) {
-      if (object.error() == simdjson::INCORRECT_TYPE) {
-        return Status::TypeError("Not a JSON object");
-      }
-      return Status::Invalid("JSON parse error: ",
-                             simdjson::error_message(object.error()));
-    }
-
-    return Status::OK();
-  }
-
-  Result<std::string> GetString(const char* key) {
-    document_.rewind();
-
-    auto object = document_.get_object();
-
-    auto field = object.find_field(key);
-
-    if (field.error() == simdjson::NO_SUCH_FIELD) {
-      return Status::KeyError("Key '", key, "' does not exist");
-    }
-    if (field.error()) {
-      return Status::Invalid("Error accessing key '", key,
-                             "': ", simdjson::error_message(field.error()));
-    }
-
-    auto str_result = field.get_string();
-    if (str_result.error() == simdjson::INCORRECT_TYPE) {
-      return Status::TypeError("Key '", key, "' is not a string");
-    }
-    if (str_result.error()) {
-      return Status::Invalid("Error getting string for key '", key,
-                             "': ", 
simdjson::error_message(str_result.error()));
-    }
-
-    std::string_view str;
-    if (auto error = std::move(str_result).get(str)) {
-      return Status::Invalid("Error getting string for key '", key,
-                             "': ", simdjson::error_message(error));
-    }
-    return std::string(str);
-  }
-
-  Result<std::unordered_map<std::string, std::string>> GetStringMap() {
-    std::unordered_map<std::string, std::string> map;
-
-    document_.rewind();
-
-    auto object = document_.get_object();
-
-    for (auto field : object) {
-      std::string_view key;
-      if (auto error = field.unescaped_key().get(key)) {
-        return Status::Invalid("Error getting object key: ",
-                               simdjson::error_message(error));
-      }
-
-      auto value = field.value();
-
-      auto str_result = value.get_string();
-
-      if (str_result.error() == simdjson::INCORRECT_TYPE) {
-        return Status::TypeError("Key '", std::string(key),
-                                 "' does not have a string value");
-      }
-      if (str_result.error()) {
-        return Status::Invalid("Error getting value for key '", 
std::string(key),
-                               "': (code=", 
static_cast<int>(str_result.error()), ")");
-      }
-
-      std::string_view str;
-      if (auto error = std::move(str_result).get(str)) {
-        return Status::Invalid("Error getting value for key '", 
std::string(key),
-                               "': ", simdjson::error_message(error));
-      }
-
-      map.emplace(std::string(key), std::string(str));
-    }
-
-    return map;
-  }
-
-  Result<bool> GetBool(const char* key) {
-    document_.rewind();
-
-    auto object = document_.get_object();
-
-    auto field = object.find_field(key);
-
-    if (field.error() == simdjson::NO_SUCH_FIELD) {
-      return Status::KeyError("Key '", key, "' does not exist");
-    }
-    if (field.error()) {
-      return Status::Invalid("Error accessing key '", key,
-                             "': ", simdjson::error_message(field.error()));
-    }
-
-    auto bool_result = field.get_bool();
-    if (bool_result.error() == simdjson::INCORRECT_TYPE) {
-      return Status::TypeError("Key '", key, "' is not a boolean");
-    }
-    if (bool_result.error()) {
-      return Status::Invalid("Error getting bool for key '", key,
-                             "': ", 
simdjson::error_message(bool_result.error()));
-    }
-
-    bool value;
-    if (auto error = std::move(bool_result).get(value)) {
-      return Status::Invalid("Error getting bool for key '", key,
-                             "': ", simdjson::error_message(error));
-    }
-
-    return value;
-  }
-
- private:
-  simdjson::ondemand::parser parser_;
-  simdjson::padded_string padded_json_;
-  simdjson::ondemand::document document_;
-};
-
-ObjectParser::ObjectParser() : impl_(new ObjectParser::Impl()) {}
-
-ObjectParser::~ObjectParser() = default;
-
-Status ObjectParser::Parse(std::string_view json) { return impl_->Parse(json); 
}
-
-Result<std::string> ObjectParser::GetString(const char* key) const {
-  return impl_->GetString(key);
-}
-
-Result<bool> ObjectParser::GetBool(const char* key) const { return 
impl_->GetBool(key); }
-
-Result<std::unordered_map<std::string, std::string>> 
ObjectParser::GetStringMap() const {
-  return impl_->GetStringMap();
-}
-
-}  // namespace internal
-}  // namespace json
-}  // namespace arrow
diff --git a/cpp/src/arrow/json/object_parser.h 
b/cpp/src/arrow/json/object_parser.h
deleted file mode 100644
index 8035695e53..0000000000
--- a/cpp/src/arrow/json/object_parser.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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 <memory>
-#include <string_view>
-#include <unordered_map>
-
-#include "arrow/result.h"
-#include "arrow/util/visibility.h"
-
-namespace arrow {
-namespace json {
-namespace internal {
-
-/// This class is a helper to parse a json object from a string.
-/// It uses rapidjson::Document in implementation.
-class ARROW_EXPORT ObjectParser {
- public:
-  ObjectParser();
-  ~ObjectParser();
-
-  Status Parse(std::string_view json);
-
-  Result<std::string> GetString(const char* key) const;
-
-  Result<bool> GetBool(const char* key) const;
-
-  // Get all members of the object as a map from string keys to string values
-  Result<std::unordered_map<std::string, std::string>> GetStringMap() const;
-
- private:
-  class Impl;
-  std::unique_ptr<Impl> impl_;
-};
-
-}  // namespace internal
-}  // namespace json
-}  // namespace arrow
diff --git a/cpp/src/arrow/json/object_parser_test.cc 
b/cpp/src/arrow/json/object_parser_test.cc
deleted file mode 100644
index b9465aee4c..0000000000
--- a/cpp/src/arrow/json/object_parser_test.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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/object_parser.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <string>
-#include <unordered_map>
-
-#include "arrow/testing/gtest_util.h"
-
-namespace arrow {
-namespace json {
-namespace internal {
-
-TEST(ObjectParser, GetString) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({"name":"arrow"})"));
-
-  ASSERT_OK_AND_ASSIGN(auto value, parser.GetString("name"));
-  EXPECT_EQ(value, "arrow");
-}
-
-TEST(ObjectParser, GetBool) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({"enabled":true})"));
-
-  ASSERT_OK_AND_ASSIGN(auto value, parser.GetBool("enabled"));
-  EXPECT_TRUE(value);
-}
-
-TEST(ObjectParser, InvalidJson) {
-  ObjectParser parser;
-
-  EXPECT_RAISES_WITH_MESSAGE_THAT(Invalid, ::testing::HasSubstr("JSON parse 
error"),
-                                  parser.Parse(R"({"name":)"));
-}
-
-TEST(ObjectParser, GetStringMap) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({
-    "k1": "v1",
-    "k2": "v2"
-  })"));
-
-  ASSERT_OK_AND_ASSIGN(auto map, parser.GetStringMap());
-
-  ASSERT_EQ(map.size(), 2U);
-  EXPECT_EQ(map["k1"], "v1");
-  EXPECT_EQ(map["k2"], "v2");
-}
-
-TEST(ObjectParser, MissingKey) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({
-    "name": "arrow"
-  })"));
-
-  ASSERT_RAISES(KeyError, parser.GetString("missing"));
-  ASSERT_RAISES(KeyError, parser.GetBool("missing"));
-}
-
-TEST(ObjectParser, WrongType) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({
-    "flag": true,
-    "name": "arrow"
-  })"));
-
-  ASSERT_RAISES(TypeError, parser.GetString("flag"));
-  ASSERT_RAISES(TypeError, parser.GetBool("name"));
-}
-
-TEST(ObjectParser, NonObjectRoot) {
-  ObjectParser parser;
-
-  ASSERT_RAISES(TypeError, parser.Parse(R"(["a", "b"])"));
-}
-
-TEST(ObjectParser, EmptyObject) {
-  ObjectParser parser;
-
-  ASSERT_OK(parser.Parse(R"({})"));
-
-  ASSERT_OK_AND_ASSIGN(auto map, parser.GetStringMap());
-
-  EXPECT_TRUE(map.empty());
-}
-
-}  // namespace internal
-}  // namespace json
-}  // namespace arrow
diff --git a/cpp/src/arrow/meson.build b/cpp/src/arrow/meson.build
index b5443c28cf..f181321aaa 100644
--- a/cpp/src/arrow/meson.build
+++ b/cpp/src/arrow/meson.build
@@ -18,6 +18,22 @@
 dl_dep = dependency('dl')
 threads_dep = dependency('threads')
 
+if needs_simdjson
+    simdjson_dep = dependency('simdjson', allow_fallback: false, required: 
false)
+
+    if not simdjson_dep.found()
+        cmake = import('cmake')
+        simdjson_opts = cmake.subproject_options()
+
+        simdjson_opts.add_cmake_defines({'SIMDJSON_EXCEPTIONS': 'OFF'})
+
+        simdjson_proj = cmake.subproject('simdjson', options: simdjson_opts)
+        simdjson_dep = simdjson_proj.dependency('simdjson')
+    endif
+else
+    simdjson_dep = disabler()
+endif
+
 arrow_components = {
     'arrow_array': {
         'sources': [
@@ -224,6 +240,11 @@ arrow_util_srcs = [
 
 arrow_util_deps = [threads_dep]
 
+if needs_simdjson
+    arrow_util_srcs += ['util/simdjson_internal.cc']
+    arrow_util_deps += [simdjson_dep]
+endif
+
 if needs_brotli
     arrow_util_srcs += ['util/compression_brotli.cc']
     arrow_util_deps += [dependency('libbrotlidec'), dependency('libbrotlienc')]
@@ -318,22 +339,6 @@ else
     rapidjson_dep = disabler()
 endif
 
-if needs_json or needs_integration
-    simdjson_dep = dependency('simdjson', allow_fallback: false, required: 
false)
-
-    if not simdjson_dep.found()
-        cmake = import('cmake')
-        simdjson_opts = cmake.subproject_options()
-
-        simdjson_opts.add_cmake_defines({'SIMDJSON_EXCEPTIONS': 'OFF'})
-
-        simdjson_proj = cmake.subproject('simdjson', options: simdjson_opts)
-        simdjson_dep = simdjson_proj.dependency('simdjson')
-    endif
-else
-    simdjson_dep = disabler()
-endif
-
 azure_dep = disabler()
 gcs_dep = disabler()
 s3_dep = disabler()
@@ -530,8 +535,6 @@ if needs_json
                 'json/chunker.cc',
                 'json/converter.cc',
                 'json/from_string.cc',
-                'json/json_writer_internal.cc',
-                'json/object_parser.cc',
                 'json/options.cc',
                 'json/parser.cc',
                 'json/reader.cc',
diff --git a/cpp/src/arrow/util/CMakeLists.txt 
b/cpp/src/arrow/util/CMakeLists.txt
index 628e9a4d1c..c67abf55a2 100644
--- a/cpp/src/arrow/util/CMakeLists.txt
+++ b/cpp/src/arrow/util/CMakeLists.txt
@@ -87,6 +87,14 @@ add_arrow_test(utility-test
                EXTRA_LINK_LIBS
                ${ARROW_UTILITY_TEST_LINK_LIBS})
 
+if(ARROW_WITH_SIMDJSON)
+  add_arrow_test(simdjson-internal-test
+                 SOURCES
+                 simdjson_internal_test.cc
+                 EXTRA_LINK_LIBS
+                 arrow::simdjson)
+endif()
+
 add_arrow_test(async-utility-test
                SOURCES
                async_generator_test.cc
diff --git a/cpp/src/arrow/util/meson.build b/cpp/src/arrow/util/meson.build
index c39e09c2d0..729cfba472 100644
--- a/cpp/src/arrow/util/meson.build
+++ b/cpp/src/arrow/util/meson.build
@@ -222,6 +222,12 @@ utility_test_srcs = [
     'value_parsing_test.cc',
 ]
 
+utility_test_deps = [arrow_test_dep_no_main]
+if needs_simdjson
+    utility_test_srcs += ['simdjson_internal_test.cc']
+    utility_test_deps += [simdjson_dep]
+endif
+
 if host_machine.system() == 'windows'
     # This manifest enables long file paths on Windows 10+
     # See 
https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#enable-long-paths-in-windows-10-version-1607-and-later
@@ -235,7 +241,7 @@ endif
 exc = executable(
     'arrow-utility-test',
     sources: utility_test_srcs,
-    dependencies: arrow_test_dep_no_main,
+    dependencies: utility_test_deps,
     implicit_include_directories: false,
 )
 test('arrow-utility-test', exc)
diff --git a/cpp/src/arrow/util/simdjson_internal.cc 
b/cpp/src/arrow/util/simdjson_internal.cc
new file mode 100644
index 0000000000..146b48d9eb
--- /dev/null
+++ b/cpp/src/arrow/util/simdjson_internal.cc
@@ -0,0 +1,584 @@
+// 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/util/simdjson_internal.h"
+
+namespace arrow::internal {
+
+class JsonObjectParser::Impl {
+ public:
+  Status Parse(std::string_view json) {
+    // Copy into padded buffer
+    padded_json_ = simdjson::padded_string(json);
+
+    // Store parsed document
+    if (auto error = parser_.iterate(padded_json_).get(document_)) {
+      return Status::Invalid("JSON parse error: ", 
simdjson::error_message(error));
+    }
+
+    // Validate root is an object
+    auto object = document_.get_object();
+    if (object.error()) {
+      if (object.error() == simdjson::INCORRECT_TYPE) {
+        return Status::TypeError("Not a JSON object");
+      }
+      return Status::Invalid("JSON parse error: ",
+                             simdjson::error_message(object.error()));
+    }
+
+    return Status::OK();
+  }
+
+  Result<std::string> GetString(const char* key) {
+    document_.rewind();
+
+    auto object = document_.get_object();
+
+    auto field = object.find_field(key);
+
+    if (field.error() == simdjson::NO_SUCH_FIELD) {
+      return Status::KeyError("Key '", key, "' does not exist");
+    }
+    if (field.error()) {
+      return Status::Invalid("Error accessing key '", key,
+                             "': ", simdjson::error_message(field.error()));
+    }
+
+    auto str_result = field.get_string();
+    if (str_result.error() == simdjson::INCORRECT_TYPE) {
+      return Status::TypeError("Key '", key, "' is not a string");
+    }
+    if (str_result.error()) {
+      return Status::Invalid("Error getting string for key '", key,
+                             "': ", 
simdjson::error_message(str_result.error()));
+    }
+
+    std::string_view str;
+    if (auto error = std::move(str_result).get(str)) {
+      return Status::Invalid("Error getting string for key '", key,
+                             "': ", simdjson::error_message(error));
+    }
+    return std::string(str);
+  }
+
+  Result<std::unordered_map<std::string, std::string>> GetStringMap() {
+    std::unordered_map<std::string, std::string> map;
+
+    document_.rewind();
+
+    auto object = document_.get_object();
+
+    for (auto field : object) {
+      std::string_view key;
+      if (auto error = field.unescaped_key().get(key)) {
+        return Status::Invalid("Error getting object key: ",
+                               simdjson::error_message(error));
+      }
+
+      auto value = field.value();
+
+      auto str_result = value.get_string();
+
+      if (str_result.error() == simdjson::INCORRECT_TYPE) {
+        return Status::TypeError("Key '", std::string(key),
+                                 "' does not have a string value");
+      }
+      if (str_result.error()) {
+        return Status::Invalid("Error getting value for key '", 
std::string(key),
+                               "': (code=", 
static_cast<int>(str_result.error()), ")");
+      }
+
+      std::string_view str;
+      if (auto error = std::move(str_result).get(str)) {
+        return Status::Invalid("Error getting value for key '", 
std::string(key),
+                               "': ", simdjson::error_message(error));
+      }
+
+      map.emplace(std::string(key), std::string(str));
+    }
+
+    return map;
+  }
+
+  Result<bool> GetBool(const char* key) {
+    document_.rewind();
+
+    auto object = document_.get_object();
+
+    auto field = object.find_field(key);
+
+    if (field.error() == simdjson::NO_SUCH_FIELD) {
+      return Status::KeyError("Key '", key, "' does not exist");
+    }
+    if (field.error()) {
+      return Status::Invalid("Error accessing key '", key,
+                             "': ", simdjson::error_message(field.error()));
+    }
+
+    auto bool_result = field.get_bool();
+    if (bool_result.error() == simdjson::INCORRECT_TYPE) {
+      return Status::TypeError("Key '", key, "' is not a boolean");
+    }
+    if (bool_result.error()) {
+      return Status::Invalid("Error getting bool for key '", key,
+                             "': ", 
simdjson::error_message(bool_result.error()));
+    }
+
+    bool value;
+    if (auto error = std::move(bool_result).get(value)) {
+      return Status::Invalid("Error getting bool for key '", key,
+                             "': ", simdjson::error_message(error));
+    }
+
+    return value;
+  }
+
+ private:
+  simdjson::ondemand::parser parser_;
+  simdjson::padded_string padded_json_;
+  simdjson::ondemand::document document_;
+};
+
+JsonObjectParser::JsonObjectParser() : impl_(new JsonObjectParser::Impl()) {}
+
+JsonObjectParser::~JsonObjectParser() = default;
+
+Status JsonObjectParser::Parse(std::string_view json) { return 
impl_->Parse(json); }
+
+Result<std::string> JsonObjectParser::GetString(const char* key) const {
+  return impl_->GetString(key);
+}
+
+Result<bool> JsonObjectParser::GetBool(const char* key) const {
+  return impl_->GetBool(key);
+}
+
+Result<std::unordered_map<std::string, std::string>> 
JsonObjectParser::GetStringMap()
+    const {
+  return impl_->GetStringMap();
+}
+
+namespace sj = simdjson::ondemand;
+
+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;
+}
+
+Status JsonWriter::WriteValue(sj::value value) {
+  return VisitJsonValue(
+      value,
+
+      [&](sj::object object) -> Status {
+        StartObject();
+
+        for (auto field : object) {
+          ARROW_ASSIGN_OR_RAISE(
+              auto key,
+              ResolveSimdjsonResult(field.unescaped_key(), "Failed to get 
object key"));
+
+          Key(key);
+
+          ARROW_ASSIGN_OR_RAISE(
+              auto field_value,
+              ResolveSimdjsonResult(field.value(), "Failed to get object 
value"));
+
+          RETURN_NOT_OK(WriteValue(field_value));
+        }
+
+        EndObject();
+        return Status::OK();
+      },
+
+      [&](sj::array array) -> Status {
+        StartArray();
+
+        for (auto element : array) {
+          ARROW_ASSIGN_OR_RAISE(
+              auto element_value,
+              ResolveSimdjsonResult(element, "Failed to iterate JSON array"));
+
+          RETURN_NOT_OK(WriteValue(element_value));
+        }
+
+        EndArray();
+        return Status::OK();
+      },
+
+      [&](std::string_view string_value) -> Status {
+        String(string_value);
+        return Status::OK();
+      },
+
+      [&](bool bool_value) -> Status {
+        Bool(bool_value);
+        return Status::OK();
+      },
+
+      [&]() -> Status {
+        Null();
+        return Status::OK();
+      },
+
+      [&](int64_t value) -> Status {
+        Int64(value);
+        return Status::OK();
+      },
+
+      [&](uint64_t value) -> Status {
+        Uint64(value);
+        return Status::OK();
+      },
+
+      [&](double value) -> Status {
+        Double(value);
+        return Status::OK();
+      },
+
+      [&](sj::value value) -> Status {
+        ARROW_ASSIGN_OR_RAISE(auto raw_json,
+                              
ResolveSimdjsonResult(simdjson::to_json_string(value),
+                                                    "Failed to get raw JSON"));
+        RawValue(raw_json);
+        return Status::OK();
+      });
+}
+
+void JsonWriter::Null() {
+  MaybeComma();
+  builder_.append_null();
+  needs_comma_ = true;
+}
+
+Result<std::string_view> JsonWriter::GetString() const {
+  std::string_view view;
+  if (auto error = builder_.view().get(view); error != simdjson::SUCCESS) {
+    if (error == simdjson::OUT_OF_CAPACITY) {
+      return Status::OutOfMemory(
+          "OutOfMemory when allocating buffer to serialize json to string");
+    }
+    return Status::Invalid("Failed to retrieve json from string builder: ",
+                           simdjson::error_message(error));
+  }
+  return view;
+}
+
+Result<std::string> JsonWriter::GetPrettyString(
+    const simdjson::fractured_json_options& options) const {
+  ARROW_ASSIGN_OR_RAISE(std::string_view json, GetString());
+  return simdjson::fractured_json_string(json, options);
+}
+
+void JsonWriter::Clear() {
+  builder_.clear();
+  needs_comma_ = false;
+}
+
+void JsonWriter::MaybeComma() {
+  if (needs_comma_) {
+    builder_.append_comma();
+  }
+}
+
+void JsonWriter::StringField(std::string_view key, std::string_view value) {
+  Key(key);
+  String(value);
+}
+
+void JsonWriter::BoolField(std::string_view key, bool value) {
+  Key(key);
+  Bool(value);
+}
+
+void JsonWriter::IntField(std::string_view key, int32_t value) {
+  Key(key);
+  Int(value);
+}
+
+const char* JsonTypeName(simdjson::dom::element_type type) {
+  switch (type) {
+    case simdjson::dom::element_type::ARRAY:
+      return "array";
+    case simdjson::dom::element_type::OBJECT:
+      return "object";
+    case simdjson::dom::element_type::INT64:
+    case simdjson::dom::element_type::UINT64:
+    case simdjson::dom::element_type::DOUBLE:
+      return "number";
+    case simdjson::dom::element_type::STRING:
+      return "string";
+    case simdjson::dom::element_type::BOOL:
+      return "boolean";
+    case simdjson::dom::element_type::NULL_VALUE:
+      return "null";
+    default:
+      return "unknown";
+  }
+}
+
+Result<simdjson::dom::array> GetJsonArray(simdjson::dom::element value,
+                                          std::string_view name) {
+  if (!value.is_array()) {
+    return Status::Invalid(name, " must be an array, got ", 
JsonTypeName(value.type()));
+  }
+  return ResolveSimdjsonResult(value.get_array(), "Failed to get JSON array");
+}
+
+Result<int64_t> GetJsonInt(simdjson::dom::element value, std::string_view name,
+                           std::string_view expected) {
+  if (!value.is_int64()) {
+    return Status::Invalid(name, " must contain ", expected, ", got ",
+                           JsonTypeName(value.type()));
+  }
+  return ResolveSimdjsonResult(value.get_int64(), "Failed to get JSON 
integer");
+}
+
+Result<simdjson::dom::object> ParseJsonObject(simdjson::dom::parser& parser,
+                                              const std::string& json) {
+  return ResolveSimdjsonResult(parser.parse(json).get_object(),
+                               "Invalid serialized JSON data");
+}
+
+Result<std::optional<simdjson::dom::element>> GetOptionalJsonField(
+    const simdjson::dom::object& object, std::string_view key) {
+  auto field = object.at_key(key);
+  if (field.error() == simdjson::NO_SUCH_FIELD) {
+    return std::nullopt;
+  }
+
+  ARROW_ASSIGN_OR_RAISE(
+      auto value,
+      ResolveSimdjsonResult(std::move(field), "Failed to get JSON object 
field"));
+
+  return std::optional<simdjson::dom::element>(std::move(value));
+}
+
+Result<std::vector<int64_t>> GetJsonIntArray(simdjson::dom::element value,
+                                             std::string_view name) {
+  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
+
+  std::vector<int64_t> result;
+  result.reserve(array.size());
+
+  for (auto element : array) {
+    ARROW_ASSIGN_OR_RAISE(auto number, GetJsonInt(element, name, "integers"));
+    result.push_back(number);
+  }
+
+  return result;
+}
+
+Result<std::vector<std::optional<int64_t>>> GetJsonNullableIntArray(
+    simdjson::dom::element value, std::string_view name) {
+  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
+
+  std::vector<std::optional<int64_t>> result;
+  result.reserve(array.size());
+
+  for (auto element : array) {
+    if (element.is_null()) {
+      result.emplace_back(std::nullopt);
+    } else {
+      ARROW_ASSIGN_OR_RAISE(auto number, GetJsonInt(element, name, "integers 
or nulls"));
+      result.emplace_back(number);
+    }
+  }
+
+  return result;
+}
+
+Result<std::vector<std::string>> GetJsonStringArray(simdjson::dom::element 
value,
+                                                    std::string_view name) {
+  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
+
+  std::vector<std::string> result;
+  result.reserve(array.size());
+
+  for (auto element : array) {
+    if (!element.is_string()) {
+      return Status::Invalid(name, " must contain strings, got ",
+                             JsonTypeName(element.type()));
+    }
+
+    ARROW_ASSIGN_OR_RAISE(
+        auto string,
+        ResolveSimdjsonResult(element.get_string(), "Failed to get JSON 
string"));
+    result.emplace_back(string);
+  }
+
+  return result;
+}
+
+const char* JsonTypeName(simdjson::ondemand::json_type type) {
+  switch (type) {
+    case simdjson::ondemand::json_type::array:
+      return "array";
+    case simdjson::ondemand::json_type::object:
+      return "object";
+    case simdjson::ondemand::json_type::number:
+      return "number";
+    case simdjson::ondemand::json_type::string:
+      return "string";
+    case simdjson::ondemand::json_type::boolean:
+      return "boolean";
+    case simdjson::ondemand::json_type::null:
+      return "null";
+    default:
+      return "unknown";
+  }
+}
+
+Result<bool> IsJsonNull(simdjson::ondemand::value& value) {
+  bool is_null;
+  auto error_code = value.is_null().get(is_null);
+  if (error_code != simdjson::SUCCESS) {
+    return Status::Invalid("Error checking for JSON null: ",
+                           simdjson::error_message(error_code));
+  }
+  return is_null;
+}
+
+Result<std::string> MinifyJson(std::string_view json) {
+  std::string minified(json.size(), '\0');
+  size_t minified_len = 0;
+
+  if (auto error =
+          simdjson::minify(json.data(), json.size(), minified.data(), 
minified_len);
+      error != simdjson::SUCCESS) {
+    return Status::Invalid("Failed to minify JSON: ", 
simdjson::error_message(error));
+  }
+
+  minified.resize(minified_len);
+  return minified;
+}
+
+Status ConsumeJsonValue(simdjson::ondemand::value value) {
+  return VisitJsonValue(
+      value, ValidateJsonObject, ValidateJsonArray,
+      [](std::string_view) { return Status::OK(); }, [](bool) { return 
Status::OK(); },
+      []() { return Status::OK(); }, [](int64_t) { return Status::OK(); },
+      [](uint64_t) { return Status::OK(); }, [](double) { return Status::OK(); 
},
+      [](simdjson::ondemand::value) { return Status::OK(); });
+}
+
+Status ValidateJsonObject(simdjson::ondemand::object object) {
+  for (auto field_result : object) {
+    ARROW_ASSIGN_OR_RAISE(
+        auto field, ResolveSimdjsonResult(field_result, "Failed to iterate 
JSON object"));
+
+    RETURN_NOT_OK(ConsumeJsonValue(field.value()));
+  }
+
+  return Status::OK();
+}
+
+Status ValidateJsonArray(simdjson::ondemand::array array) {
+  for (auto element_result : array) {
+    ARROW_ASSIGN_OR_RAISE(
+        auto value,
+        ResolveSimdjsonResult(element_result, "Failed to iterate JSON array"));
+
+    RETURN_NOT_OK(ConsumeJsonValue(value));
+  }
+
+  return Status::OK();
+}
+
+Status ValidateJsonDocument(simdjson::ondemand::parser& parser,
+                            simdjson::padded_string& json) {
+  ARROW_ASSIGN_OR_RAISE(
+      auto document, ResolveSimdjsonResult(parser.iterate(json), "Failed to 
parse JSON"));
+
+  ARROW_ASSIGN_OR_RAISE(auto value, ResolveSimdjsonResult(document.get_value(),
+                                                          "Failed to get JSON 
value"));
+
+  return ConsumeJsonValue(value);
+}
+
+}  // namespace arrow::internal
diff --git a/cpp/src/arrow/util/simdjson_internal.h 
b/cpp/src/arrow/util/simdjson_internal.h
index 799ebfc5ea..5c52f7648c 100644
--- a/cpp/src/arrow/util/simdjson_internal.h
+++ b/cpp/src/arrow/util/simdjson_internal.h
@@ -19,9 +19,11 @@
 
 #include <concepts>
 #include <cstdint>
+#include <memory>
 #include <optional>
 #include <string>
 #include <string_view>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -29,9 +31,76 @@
 
 #include "arrow/result.h"
 #include "arrow/status.h"
+#include "arrow/util/visibility.h"
 
-namespace arrow {
-namespace internal {
+namespace arrow::internal {
+
+/// This class is a helper to parse a JSON object from a string.
+/// It uses simdjson in the implementation.
+class ARROW_EXPORT JsonObjectParser {
+ public:
+  JsonObjectParser();
+  ~JsonObjectParser();
+
+  Status Parse(std::string_view json);
+
+  Result<std::string> GetString(const char* key) const;
+
+  Result<bool> GetBool(const char* key) const;
+
+  // Get all members of the object as a map from string keys to string values
+  Result<std::unordered_map<std::string, std::string>> GetStringMap() const;
+
+ private:
+  class Impl;
+  std::unique_ptr<Impl> impl_;
+};
+
+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);
+
+  Status WriteValue(simdjson::ondemand::value value);
+
+  void Null();
+
+  void StringField(std::string_view key, std::string_view value);
+  void BoolField(std::string_view key, bool value);
+  void IntField(std::string_view key, int32_t value);
+
+  Result<std::string_view> GetString() const;
+
+  Result<std::string> GetPrettyString(
+      const simdjson::fractured_json_options& options = {}) const;
+
+  void Clear();
+
+ private:
+  void MaybeComma();
+
+  simdjson::builder::string_builder builder_;
+  bool needs_comma_ = false;
+};
 
 // Empty struct to represent the type of a simdjson null value
 struct SimdjsonNull {};
@@ -94,121 +163,30 @@ Result<T> 
ResolveSimdjsonResult(simdjson::simdjson_result<T> result,
   return value;
 }
 
-inline const char* JsonTypeName(simdjson::dom::element_type type) {
-  switch (type) {
-    case simdjson::dom::element_type::ARRAY:
-      return "array";
-    case simdjson::dom::element_type::OBJECT:
-      return "object";
-    case simdjson::dom::element_type::INT64:
-    case simdjson::dom::element_type::UINT64:
-    case simdjson::dom::element_type::DOUBLE:
-      return "number";
-    case simdjson::dom::element_type::STRING:
-      return "string";
-    case simdjson::dom::element_type::BOOL:
-      return "boolean";
-    case simdjson::dom::element_type::NULL_VALUE:
-      return "null";
-    default:
-      return "unknown";
-  }
-}
+ARROW_EXPORT const char* JsonTypeName(simdjson::dom::element_type type);
 
-inline Result<simdjson::dom::array> GetJsonArray(simdjson::dom::element value,
-                                                 std::string_view name) {
-  if (!value.is_array()) {
-    return Status::Invalid(name, " must be an array, got ", 
JsonTypeName(value.type()));
-  }
-  return ResolveSimdjsonResult(value.get_array(), "Failed to get JSON array");
-}
+ARROW_EXPORT Result<simdjson::dom::array> GetJsonArray(simdjson::dom::element 
value,
+                                                       std::string_view name);
 
-inline Result<int64_t> GetJsonInt(simdjson::dom::element value, 
std::string_view name,
-                                  std::string_view expected) {
-  if (!value.is_int64()) {
-    return Status::Invalid(name, " must contain ", expected, ", got ",
-                           JsonTypeName(value.type()));
-  }
-  return ResolveSimdjsonResult(value.get_int64(), "Failed to get JSON 
integer");
-}
+ARROW_EXPORT Result<int64_t> GetJsonInt(simdjson::dom::element value,
+                                        std::string_view name, 
std::string_view expected);
 
-inline Result<simdjson::dom::object> ParseJsonObject(simdjson::dom::parser& 
parser,
-                                                     const std::string& json) {
-  return ResolveSimdjsonResult(parser.parse(json).get_object(),
-                               "Invalid serialized JSON data");
-}
+ARROW_EXPORT Result<simdjson::dom::object> 
ParseJsonObject(simdjson::dom::parser& parser,
+                                                           const std::string& 
json);
 
 // object.at_key() performs a linear search. This is acceptable here
 // since these objects are expected to contain only a small number of fields.
-inline Result<std::optional<simdjson::dom::element>> GetOptionalJsonField(
-    const simdjson::dom::object& object, std::string_view key) {
-  auto field = object.at_key(key);
-  if (field.error() == simdjson::NO_SUCH_FIELD) {
-    return std::nullopt;
-  }
+ARROW_EXPORT Result<std::optional<simdjson::dom::element>> 
GetOptionalJsonField(
+    const simdjson::dom::object& object, std::string_view key);
 
-  ARROW_ASSIGN_OR_RAISE(
-      auto value,
-      ResolveSimdjsonResult(std::move(field), "Failed to get JSON object 
field"));
-
-  return std::optional<simdjson::dom::element>(std::move(value));
-}
+ARROW_EXPORT Result<std::vector<int64_t>> 
GetJsonIntArray(simdjson::dom::element value,
+                                                          std::string_view 
name);
 
-inline Result<std::vector<int64_t>> GetJsonIntArray(simdjson::dom::element 
value,
-                                                    std::string_view name) {
-  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
+ARROW_EXPORT Result<std::vector<std::optional<int64_t>>> 
GetJsonNullableIntArray(
+    simdjson::dom::element value, std::string_view name);
 
-  std::vector<int64_t> result;
-  result.reserve(array.size());
-
-  for (auto element : array) {
-    ARROW_ASSIGN_OR_RAISE(auto number, GetJsonInt(element, name, "integers"));
-    result.push_back(number);
-  }
-
-  return result;
-}
-
-inline Result<std::vector<std::optional<int64_t>>> GetJsonNullableIntArray(
-    simdjson::dom::element value, std::string_view name) {
-  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
-
-  std::vector<std::optional<int64_t>> result;
-  result.reserve(array.size());
-
-  for (auto element : array) {
-    if (element.is_null()) {
-      result.emplace_back(std::nullopt);
-    } else {
-      ARROW_ASSIGN_OR_RAISE(auto number, GetJsonInt(element, name, "integers 
or nulls"));
-      result.emplace_back(number);
-    }
-  }
-
-  return result;
-}
-
-inline Result<std::vector<std::string>> 
GetJsonStringArray(simdjson::dom::element value,
-                                                           std::string_view 
name) {
-  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonArray(value, name));
-
-  std::vector<std::string> result;
-  result.reserve(array.size());
-
-  for (auto element : array) {
-    if (!element.is_string()) {
-      return Status::Invalid(name, " must contain strings, got ",
-                             JsonTypeName(element.type()));
-    }
-
-    ARROW_ASSIGN_OR_RAISE(
-        auto string,
-        ResolveSimdjsonResult(element.get_string(), "Failed to get JSON 
string"));
-    result.emplace_back(string);
-  }
-
-  return result;
-}
+ARROW_EXPORT Result<std::vector<std::string>> GetJsonStringArray(
+    simdjson::dom::element value, std::string_view name);
 
 template <typename ObjectFn, typename ArrayFn, typename StringFn, typename 
BoolFn,
           typename NullFn, typename Int64Fn, typename Uint64Fn, typename 
DoubleFn,
@@ -294,35 +272,10 @@ Status VisitJsonValue(simdjson::ondemand::value value, 
ObjectFn&& object_fn,
   return Status::Invalid("Unreachable");
 }
 
-inline const char* JsonTypeName(simdjson::ondemand::json_type type) {
-  switch (type) {
-    case simdjson::ondemand::json_type::array:
-      return "array";
-    case simdjson::ondemand::json_type::object:
-      return "object";
-    case simdjson::ondemand::json_type::number:
-      return "number";
-    case simdjson::ondemand::json_type::string:
-      return "string";
-    case simdjson::ondemand::json_type::boolean:
-      return "boolean";
-    case simdjson::ondemand::json_type::null:
-      return "null";
-    default:
-      return "unknown";
-  }
-}
+ARROW_EXPORT const char* JsonTypeName(simdjson::ondemand::json_type type);
 
 // Result<bool> because peeking the nonRootScalar can fail (parsed lazily)
-inline Result<bool> IsJsonNull(simdjson::ondemand::value& value) {
-  bool is_null;
-  auto error_code = value.is_null().get(is_null);
-  if (error_code != simdjson::SUCCESS) {
-    return Status::Invalid("Error checking for JSON null: ",
-                           simdjson::error_message(error_code));
-  }
-  return is_null;
-}
+ARROW_EXPORT Result<bool> IsJsonNull(simdjson::ondemand::value& value);
 
 template <typename SimdjsonValueType>
 Result<SimdjsonValueType> GetJsonAs(simdjson::ondemand::value& value) {
@@ -386,66 +339,15 @@ Result<T> GetJsonField(simdjson::ondemand::object& 
object, std::string_view key)
   return Status::KeyError("Missing JSON field: ", key);
 }
 
-inline Result<std::string> MinifyJson(std::string_view json) {
-  std::string minified(json.size(), '\0');
-  size_t minified_len = 0;
+ARROW_EXPORT Result<std::string> MinifyJson(std::string_view json);
 
-  if (auto error =
-          simdjson::minify(json.data(), json.size(), minified.data(), 
minified_len);
-      error != simdjson::SUCCESS) {
-    return Status::Invalid("Failed to minify JSON: ", 
simdjson::error_message(error));
-  }
+ARROW_EXPORT Status ValidateJsonObject(simdjson::ondemand::object object);
 
-  minified.resize(minified_len);
-  return minified;
-}
+ARROW_EXPORT Status ValidateJsonArray(simdjson::ondemand::array array);
 
-inline Status ValidateJsonObject(simdjson::ondemand::object object);
+ARROW_EXPORT Status ConsumeJsonValue(simdjson::ondemand::value value);
 
-inline Status ValidateJsonArray(simdjson::ondemand::array array);
-
-inline Status ConsumeJsonValue(simdjson::ondemand::value value) {
-  return VisitJsonValue(
-      value, ValidateJsonObject, ValidateJsonArray,
-      [](std::string_view) { return Status::OK(); }, [](bool) { return 
Status::OK(); },
-      []() { return Status::OK(); }, [](int64_t) { return Status::OK(); },
-      [](uint64_t) { return Status::OK(); }, [](double) { return Status::OK(); 
},
-      [](simdjson::ondemand::value) { return Status::OK(); });
-}
-
-inline Status ValidateJsonObject(simdjson::ondemand::object object) {
-  for (auto field_result : object) {
-    ARROW_ASSIGN_OR_RAISE(
-        auto field, ResolveSimdjsonResult(field_result, "Failed to iterate 
JSON object"));
-
-    RETURN_NOT_OK(ConsumeJsonValue(field.value()));
-  }
-
-  return Status::OK();
-}
-
-inline Status ValidateJsonArray(simdjson::ondemand::array array) {
-  for (auto element_result : array) {
-    ARROW_ASSIGN_OR_RAISE(
-        auto value,
-        ResolveSimdjsonResult(element_result, "Failed to iterate JSON array"));
-
-    RETURN_NOT_OK(ConsumeJsonValue(value));
-  }
-
-  return Status::OK();
-}
-
-inline Status ValidateJsonDocument(simdjson::ondemand::parser& parser,
-                                   simdjson::padded_string& json) {
-  ARROW_ASSIGN_OR_RAISE(
-      auto document, ResolveSimdjsonResult(parser.iterate(json), "Failed to 
parse JSON"));
-
-  ARROW_ASSIGN_OR_RAISE(auto value, ResolveSimdjsonResult(document.get_value(),
-                                                          "Failed to get JSON 
value"));
-
-  return ConsumeJsonValue(value);
-}
+ARROW_EXPORT Status ValidateJsonDocument(simdjson::ondemand::parser& parser,
+                                         simdjson::padded_string& json);
 
-}  // namespace internal
-}  // namespace arrow
+}  // namespace arrow::internal
diff --git a/cpp/src/arrow/json/json_writer_internal_test.cc 
b/cpp/src/arrow/util/simdjson_internal_test.cc
similarity index 81%
rename from cpp/src/arrow/json/json_writer_internal_test.cc
rename to cpp/src/arrow/util/simdjson_internal_test.cc
index a0123e8083..d376ee8ed6 100644
--- a/cpp/src/arrow/json/json_writer_internal_test.cc
+++ b/cpp/src/arrow/util/simdjson_internal_test.cc
@@ -15,14 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "arrow/json/json_writer_internal.h"
+#include <string>
+#include <unordered_map>
+
 #include "arrow/testing/gtest_util.h"
+#include "arrow/util/simdjson_internal.h"
 
 namespace sj = simdjson::ondemand;
 
-namespace arrow::json {
+namespace arrow::internal {
 
 TEST(JsonWriter, SimpleObject) {
   JsonWriter writer;
@@ -333,4 +337,83 @@ TEST(JsonWriter, GetPrettyString) {
   EXPECT_EQ(b_value, "hello");
 }
 
-}  // namespace arrow::json
+TEST(JsonObjectParser, GetString) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({"name":"arrow"})"));
+
+  ASSERT_OK_AND_ASSIGN(auto value, parser.GetString("name"));
+  EXPECT_EQ(value, "arrow");
+}
+
+TEST(JsonObjectParser, GetBool) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({"enabled":true})"));
+
+  ASSERT_OK_AND_ASSIGN(auto value, parser.GetBool("enabled"));
+  EXPECT_TRUE(value);
+}
+
+TEST(JsonObjectParser, InvalidJson) {
+  JsonObjectParser parser;
+
+  EXPECT_RAISES_WITH_MESSAGE_THAT(Invalid, ::testing::HasSubstr("JSON parse 
error"),
+                                  parser.Parse(R"({"name":)"));
+}
+
+TEST(JsonObjectParser, GetStringMap) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({
+    "k1": "v1",
+    "k2": "v2"
+  })"));
+
+  ASSERT_OK_AND_ASSIGN(auto map, parser.GetStringMap());
+
+  ASSERT_EQ(map.size(), 2U);
+  EXPECT_EQ(map["k1"], "v1");
+  EXPECT_EQ(map["k2"], "v2");
+}
+
+TEST(JsonObjectParser, MissingKey) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({
+    "name": "arrow"
+  })"));
+
+  ASSERT_RAISES(KeyError, parser.GetString("missing"));
+  ASSERT_RAISES(KeyError, parser.GetBool("missing"));
+}
+
+TEST(JsonObjectParser, WrongType) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({
+    "flag": true,
+    "name": "arrow"
+  })"));
+
+  ASSERT_RAISES(TypeError, parser.GetString("flag"));
+  ASSERT_RAISES(TypeError, parser.GetBool("name"));
+}
+
+TEST(JsonObjectParser, NonObjectRoot) {
+  JsonObjectParser parser;
+
+  ASSERT_RAISES(TypeError, parser.Parse(R"(["a", "b"])"));
+}
+
+TEST(JsonObjectParser, EmptyObject) {
+  JsonObjectParser parser;
+
+  ASSERT_OK(parser.Parse(R"({})"));
+
+  ASSERT_OK_AND_ASSIGN(auto map, parser.GetStringMap());
+
+  EXPECT_TRUE(map.empty());
+}
+
+}  // namespace arrow::internal
diff --git a/cpp/src/parquet/encryption/file_system_key_material_store.cc 
b/cpp/src/parquet/encryption/file_system_key_material_store.cc
index ece14b9664..b0ba9c9e72 100644
--- a/cpp/src/parquet/encryption/file_system_key_material_store.cc
+++ b/cpp/src/parquet/encryption/file_system_key_material_store.cc
@@ -20,9 +20,8 @@
 #include "arrow/buffer.h"
 #include "arrow/filesystem/filesystem.h"
 #include "arrow/filesystem/path_util.h"
-#include "arrow/json/json_writer_internal.h"
-#include "arrow/json/object_parser.h"
 #include "arrow/result.h"
+#include "arrow/util/simdjson_internal.h"
 
 #include "parquet/encryption/file_system_key_material_store.h"
 #include "parquet/encryption/key_material.h"
@@ -74,14 +73,14 @@ void FileSystemKeyMaterialStore::LoadKeyMaterialMap() {
   PARQUET_ASSIGN_OR_THROW(input_size, input->GetSize());
   PARQUET_ASSIGN_OR_THROW(buff, input->ReadAt(0, input_size));
   std::string buff_str = buff->ToString();
-  ::arrow::json::internal::ObjectParser parser;
+  ::arrow::internal::JsonObjectParser parser;
   auto status = parser.Parse(buff_str);
   PARQUET_THROW_NOT_OK(status);
   PARQUET_ASSIGN_OR_THROW(key_material_map_, parser.GetStringMap());
 }
 
 std::string FileSystemKeyMaterialStore::BuildKeyMaterialMapJson() {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   for (const auto& it : key_material_map_) {
     writer.StringField(it.first, it.second);
diff --git a/cpp/src/parquet/encryption/key_material.cc 
b/cpp/src/parquet/encryption/key_material.cc
index 305ad59698..2aba3cd3e4 100644
--- a/cpp/src/parquet/encryption/key_material.cc
+++ b/cpp/src/parquet/encryption/key_material.cc
@@ -17,15 +17,14 @@
 
 #include <string_view>
 
-#include "arrow/json/json_writer_internal.h"
-#include "arrow/json/object_parser.h"
+#include "arrow/util/simdjson_internal.h"
 
 #include "parquet/encryption/key_material.h"
 #include "parquet/encryption/key_metadata.h"
 #include "parquet/exception.h"
 
-using ::arrow::json::JsonWriter;
-using ::arrow::json::internal::ObjectParser;
+using ::arrow::internal::JsonObjectParser;
+using ::arrow::internal::JsonWriter;
 
 namespace parquet::encryption {
 
@@ -60,7 +59,7 @@ KeyMaterial::KeyMaterial(bool is_footer_key, const 
std::string& kms_instance_id,
       encoded_wrapped_dek_(encoded_wrapped_dek) {}
 
 KeyMaterial KeyMaterial::Parse(const std::string& key_material_string) {
-  ObjectParser json_parser;
+  JsonObjectParser json_parser;
   ::arrow::Status status = json_parser.Parse(key_material_string);
   if (!status.ok()) {
     throw ParquetException("Failed to parse key material " + 
key_material_string);
@@ -74,44 +73,37 @@ KeyMaterial KeyMaterial::Parse(const std::string& 
key_material_string) {
     throw ParquetException("Wrong key material type: " + key_material_type + " 
vs " +
                            kKeyMaterialType1);
   }
-  // Parse other fields (common to internal and external key material)
-  return Parse(&json_parser);
-}
 
-KeyMaterial KeyMaterial::Parse(const ObjectParser* key_material_json) {
   // 2. Check if "key material" belongs to file footer key
   bool is_footer_key;
-  PARQUET_ASSIGN_OR_THROW(is_footer_key, 
key_material_json->GetBool(kIsFooterKeyField));
+  PARQUET_ASSIGN_OR_THROW(is_footer_key, 
json_parser.GetBool(kIsFooterKeyField));
   std::string kms_instance_id;
   std::string kms_instance_url;
   if (is_footer_key) {
     // 3.  For footer key, extract KMS Instance ID
-    PARQUET_ASSIGN_OR_THROW(kms_instance_id,
-                            key_material_json->GetString(kKmsInstanceIdField));
+    PARQUET_ASSIGN_OR_THROW(kms_instance_id, 
json_parser.GetString(kKmsInstanceIdField));
     // 4.  For footer key, extract KMS Instance URL
     PARQUET_ASSIGN_OR_THROW(kms_instance_url,
-                            
key_material_json->GetString(kKmsInstanceUrlField));
+                            json_parser.GetString(kKmsInstanceUrlField));
   }
   // 5. Extract master key ID
   std::string master_key_id;
-  PARQUET_ASSIGN_OR_THROW(master_key_id, 
key_material_json->GetString(kMasterKeyIdField));
+  PARQUET_ASSIGN_OR_THROW(master_key_id, 
json_parser.GetString(kMasterKeyIdField));
   // 6. Extract wrapped DEK
   std::string encoded_wrapped_dek;
   PARQUET_ASSIGN_OR_THROW(encoded_wrapped_dek,
-                          
key_material_json->GetString(kWrappedDataEncryptionKeyField));
+                          
json_parser.GetString(kWrappedDataEncryptionKeyField));
   std::string kek_id;
   std::string encoded_wrapped_kek;
   // 7. Check if "key material" was generated in double wrapping mode
   bool is_double_wrapped;
-  PARQUET_ASSIGN_OR_THROW(is_double_wrapped,
-                          key_material_json->GetBool(kDoubleWrappingField));
+  PARQUET_ASSIGN_OR_THROW(is_double_wrapped, 
json_parser.GetBool(kDoubleWrappingField));
   if (is_double_wrapped) {
     // 8. In double wrapping mode, extract KEK ID
-    PARQUET_ASSIGN_OR_THROW(kek_id,
-                            
key_material_json->GetString(kKeyEncryptionKeyIdField));
+    PARQUET_ASSIGN_OR_THROW(kek_id, 
json_parser.GetString(kKeyEncryptionKeyIdField));
     // 9. In double wrapping mode, extract wrapped KEK
     PARQUET_ASSIGN_OR_THROW(encoded_wrapped_kek,
-                            
key_material_json->GetString(kWrappedKeyEncryptionKeyField));
+                            
json_parser.GetString(kWrappedKeyEncryptionKeyField));
   }
 
   return KeyMaterial(is_footer_key, kms_instance_id, kms_instance_url, 
master_key_id,
diff --git a/cpp/src/parquet/encryption/key_material.h 
b/cpp/src/parquet/encryption/key_material.h
index 3e7e862c99..2ce5ec8578 100644
--- a/cpp/src/parquet/encryption/key_material.h
+++ b/cpp/src/parquet/encryption/key_material.h
@@ -21,14 +21,6 @@
 
 #include "parquet/platform.h"
 
-namespace arrow {
-namespace json {
-namespace internal {
-class ObjectParser;
-}  // namespace internal
-}  // namespace json
-}  // namespace arrow
-
 namespace parquet::encryption {
 
 // KeyMaterial class represents the "key material", keeping the information 
that allows
@@ -86,9 +78,6 @@ class PARQUET_EXPORT KeyMaterial {
 
   static KeyMaterial Parse(const std::string& key_material_string);
 
-  static KeyMaterial Parse(
-      const ::arrow::json::internal::ObjectParser* key_material_json);
-
   /// This method returns a json string that will be stored either inside a 
parquet file
   /// or in a key material store outside the parquet file.
   static std::string SerializeToJson(bool is_footer_key,
diff --git a/cpp/src/parquet/encryption/key_metadata.cc 
b/cpp/src/parquet/encryption/key_metadata.cc
index 94253c87e3..65b67bc33b 100644
--- a/cpp/src/parquet/encryption/key_metadata.cc
+++ b/cpp/src/parquet/encryption/key_metadata.cc
@@ -17,14 +17,13 @@
 
 #include <string_view>
 
-#include "arrow/json/json_writer_internal.h"
-#include "arrow/json/object_parser.h"
+#include "arrow/util/simdjson_internal.h"
 
 #include "parquet/encryption/key_metadata.h"
 #include "parquet/exception.h"
 
-using ::arrow::json::JsonWriter;
-using ::arrow::json::internal::ObjectParser;
+using ::arrow::internal::JsonObjectParser;
+using ::arrow::internal::JsonWriter;
 
 namespace parquet::encryption {
 
@@ -38,7 +37,7 @@ KeyMetadata::KeyMetadata(const KeyMaterial& key_material)
     : is_internal_storage_(true), key_material_or_reference_(key_material) {}
 
 KeyMetadata KeyMetadata::Parse(const std::string& key_metadata) {
-  ObjectParser json_parser;
+  JsonObjectParser json_parser;
   ::arrow::Status status = json_parser.Parse(key_metadata);
   if (!status.ok()) {
     throw ParquetException("Failed to parse key metadata " + key_metadata);
@@ -61,8 +60,7 @@ KeyMetadata KeyMetadata::Parse(const std::string& 
key_metadata) {
 
   if (is_internal_storage) {
     // 3.1 "key material" is stored internally, inside "key metadata" - parse 
it
-    KeyMaterial key_material = KeyMaterial::Parse(&json_parser);
-    return KeyMetadata(key_material);
+    return KeyMetadata(KeyMaterial::Parse(key_metadata));
   } else {
     // 3.2 "key material" is stored externally. "key metadata" keeps a 
reference to it
     std::string key_reference;
diff --git a/cpp/src/parquet/encryption/local_wrap_kms_client.cc 
b/cpp/src/parquet/encryption/local_wrap_kms_client.cc
index dcb6c49836..2628d9aa32 100644
--- a/cpp/src/parquet/encryption/local_wrap_kms_client.cc
+++ b/cpp/src/parquet/encryption/local_wrap_kms_client.cc
@@ -17,16 +17,15 @@
 
 #include <string_view>
 
-#include "arrow/json/json_writer_internal.h"
-#include "arrow/json/object_parser.h"
 #include "arrow/util/secure_string.h"
+#include "arrow/util/simdjson_internal.h"
 
 #include "parquet/encryption/key_toolkit_internal.h"
 #include "parquet/encryption/local_wrap_kms_client.h"
 #include "parquet/exception.h"
 
-using ::arrow::json::JsonWriter;
-using ::arrow::json::internal::ObjectParser;
+using ::arrow::internal::JsonObjectParser;
+using ::arrow::internal::JsonWriter;
 using ::arrow::util::SecureString;
 
 namespace parquet::encryption {
@@ -58,7 +57,7 @@ std::string 
LocalWrapKmsClient::LocalKeyWrap::CreateSerialized(
 
 LocalWrapKmsClient::LocalKeyWrap LocalWrapKmsClient::LocalKeyWrap::Parse(
     const std::string& wrapped_key) {
-  ObjectParser json_parser;
+  JsonObjectParser json_parser;
   auto status = json_parser.Parse(wrapped_key);
   if (!status.ok()) {
     throw ParquetException("Failed to parse local key wrap json " + 
wrapped_key);
diff --git a/cpp/src/parquet/geospatial/util_json_internal.cc 
b/cpp/src/parquet/geospatial/util_json_internal.cc
index 44efcfc8d7..6933d69279 100644
--- a/cpp/src/parquet/geospatial/util_json_internal.cc
+++ b/cpp/src/parquet/geospatial/util_json_internal.cc
@@ -20,7 +20,6 @@
 #include <string>
 
 #include "arrow/extension_type.h"
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/result.h"
 #include "arrow/util/simdjson_internal.h"
 #include "arrow/util/string.h"
@@ -185,7 +184,7 @@ namespace {
 }
 
 ::arrow::Result<std::string> EscapeJsonString(std::string_view value) {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.String(value);
 
   ARROW_ASSIGN_OR_RAISE(auto escaped, writer.GetString());
diff --git a/cpp/src/parquet/printer.cc b/cpp/src/parquet/printer.cc
index dd6a21913b..cf4decb32f 100644
--- a/cpp/src/parquet/printer.cc
+++ b/cpp/src/parquet/printer.cc
@@ -25,8 +25,8 @@
 #include <string>
 #include <vector>
 
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/util/key_value_metadata.h"
+#include "arrow/util/simdjson_internal.h"
 #include "arrow/util/string.h"
 
 #include "parquet/column_scanner.h"
@@ -256,7 +256,7 @@ void ParquetFilePrinter::DebugPrint(std::ostream& stream, 
std::list<int> selecte
 void ParquetFilePrinter::JSONPrint(std::ostream& stream, std::list<int> 
selected_columns,
                                    const char* filename) {
   const FileMetaData* file_metadata = fileReader->metadata().get();
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("FileName", filename);
   writer.StringField("Version", 
ParquetVersionToString(file_metadata->version()));
diff --git a/cpp/src/parquet/types.cc b/cpp/src/parquet/types.cc
index 534c79201c..fda5e319e0 100644
--- a/cpp/src/parquet/types.cc
+++ b/cpp/src/parquet/types.cc
@@ -24,12 +24,12 @@
 #include <sstream>
 #include <string>
 
-#include "arrow/json/json_writer_internal.h"
 #include "arrow/util/checked_cast.h"
 #include "arrow/util/compression.h"
 #include "arrow/util/decimal.h"
 #include "arrow/util/float16.h"
 #include "arrow/util/logging_internal.h"
+#include "arrow/util/simdjson_internal.h"
 
 #include "parquet/exception.h"
 #include "parquet/thrift_internal.h"
@@ -717,7 +717,7 @@ class LogicalType::Impl {
   }
 
   virtual std::string ToJSON() const {
-    ::arrow::json::JsonWriter writer;
+    ::arrow::internal::JsonWriter writer;
     writer.StartObject();
     writer.StringField("Type", ToString());
     writer.EndObject();
@@ -1174,7 +1174,7 @@ std::string LogicalType::Impl::Decimal::ToString() const {
 }
 
 std::string LogicalType::Impl::Decimal::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Decimal");
   writer.IntField("precision", precision_);
@@ -1323,7 +1323,7 @@ std::string LogicalType::Impl::Time::ToString() const {
 }
 
 std::string LogicalType::Impl::Time::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Time");
   writer.BoolField("isAdjustedToUTC", adjusted_);
@@ -1474,7 +1474,7 @@ std::string LogicalType::Impl::Timestamp::ToString() 
const {
 }
 
 std::string LogicalType::Impl::Timestamp::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Timestamp");
   writer.BoolField("isAdjustedToUTC", adjusted_);
@@ -1668,7 +1668,7 @@ std::string LogicalType::Impl::Int::ToString() const {
 }
 
 std::string LogicalType::Impl::Int::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Int");
   writer.IntField("bitWidth", width_);
@@ -1825,7 +1825,7 @@ std::string LogicalType::Impl::Geometry::ToString() const 
{
 }
 
 std::string LogicalType::Impl::Geometry::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Geometry");
   if (!crs_.empty()) {
@@ -1916,7 +1916,7 @@ std::string LogicalType::Impl::Geography::ToString() 
const {
 }
 
 std::string LogicalType::Impl::Geography::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Geography");
   if (!crs_.empty()) {
@@ -2008,7 +2008,7 @@ std::string LogicalType::Impl::Variant::ToString() const {
 }
 
 std::string LogicalType::Impl::Variant::ToJSON() const {
-  ::arrow::json::JsonWriter writer;
+  ::arrow::internal::JsonWriter writer;
   writer.StartObject();
   writer.StringField("Type", "Variant");
   writer.IntField("SpecVersion", static_cast<int>(spec_version_));

Reply via email to