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

kou 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 4d1e84b641 GH-50724: [C++] Add `JsonWriter::WriteValue` for simdjson 
values (#50725)
4d1e84b641 is described below

commit 4d1e84b6418387d987d0dbd67ae0ef02d2c41040
Author: Aaditya Srinivasan <[email protected]>
AuthorDate: Mon Aug 3 03:16:58 2026 +0530

    GH-50724: [C++] Add `JsonWriter::WriteValue` for simdjson values (#50725)
    
    ### Rationale for this change
    
    This PR continues the simdjson migration by adding support for serializing 
`simdjson::ondemand::value` directly with `JsonWriter`.
    
    This provides a reusable API for future migration work and avoids requiring 
callers to implement their own recursive serialization logic. It also 
introduces a shared helper for dispatching `simdjson::ondemand::value` based on 
its JSON type, reducing duplicated type dispatch and extraction logic.
    
    ### What changes are included in this PR?
    
    * Add `JsonWriter::WriteValue(simdjson::ondemand::value)`.
    * Add `VisitJsonValue` to centralize JSON type dispatch and `simdjson` 
value extraction.
    * Recursively serialize:
    
      * objects
      * arrays
      * strings
      * booleans
      * null values
      * numeric values
    * Add unit tests covering:
    
      * simple objects
      * nested objects
      * objects containing arrays
      * complex nested values
      * empty objects
    * Use `simdjson::ondemand::document::get_value()` in tests to obtain the 
root `ondemand::value` before serialization.
    
    ### Are these changes tested?
    
    Yes.
    
    Added unit tests for `JsonWriter::WriteValue` covering the supported JSON 
value types and nested structures.
    
    ### Are there any user-facing changes?
    
    No.
    
    Closes: #50724
    
    * GitHub Issue: #50724
    
    Authored-by: Aaditya Srinivasan <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 cpp/src/arrow/json/from_string.cc               | 203 +++++---------------
 cpp/src/arrow/json/json_writer_internal.cc      |  82 ++++++++
 cpp/src/arrow/json/json_writer_internal.h       |   3 +
 cpp/src/arrow/json/json_writer_internal_test.cc | 120 ++++++++++++
 cpp/src/arrow/util/simdjson_internal.h          | 244 ++++++++++++++++++++++++
 5 files changed, 497 insertions(+), 155 deletions(-)

diff --git a/cpp/src/arrow/json/from_string.cc 
b/cpp/src/arrow/json/from_string.cc
index 5694fbde4c..c9d9106671 100644
--- a/cpp/src/arrow/json/from_string.cc
+++ b/cpp/src/arrow/json/from_string.cc
@@ -47,6 +47,7 @@
 #include "arrow/util/decimal.h"
 #include "arrow/util/float16.h"
 #include "arrow/util/logging_internal.h"
+#include "arrow/util/simdjson_internal.h"
 #include "arrow/util/unreachable.h"
 #include "arrow/util/value_parsing.h"
 
@@ -66,116 +67,6 @@ using ::arrow::internal::checked_pointer_cast;
 
 namespace {
 
-const char* JsonTypeName(sj::json_type type) {
-  switch (type) {
-    case sj::json_type::array:
-      return "array";
-    case sj::json_type::object:
-      return "object";
-    case sj::json_type::number:
-      return "number";
-    case sj::json_type::string:
-      return "string";
-    case sj::json_type::boolean:
-      return "boolean";
-    case sj::json_type::null:
-      return "null";
-    default:
-      return "unknown";
-  }
-}
-
-// Empty struct to represent the type of a simdjson null value
-struct SimdjsonNull {};
-
-template <typename T>
-struct JsonTypeNameOf;
-
-template <>
-struct JsonTypeNameOf<sj::array> {
-  static constexpr const char* kValue = "array";
-};
-template <>
-struct JsonTypeNameOf<sj::object> {
-  static constexpr const char* kValue = "object";
-};
-template <>
-struct JsonTypeNameOf<std::string_view> {
-  static constexpr const char* kValue = "string";
-};
-template <>
-struct JsonTypeNameOf<bool> {
-  static constexpr const char* kValue = "boolean";
-};
-template <>
-struct JsonTypeNameOf<SimdjsonNull> {
-  static constexpr const char* kValue = "null";
-};
-template <>
-struct JsonTypeNameOf<int64_t> {
-  static constexpr const char* kValue = "number";
-};
-template <>
-struct JsonTypeNameOf<uint64_t> {
-  static constexpr const char* kValue = "number";
-};
-template <>
-struct JsonTypeNameOf<double> {
-  static constexpr const char* kValue = "number";
-};
-
-template <typename T>
-constexpr const char* JsonTypeName() {
-  return JsonTypeNameOf<T>::kValue;
-}
-
-template <typename SimdjsonValueType>
-Result<SimdjsonValueType> GetJsonAs(sj::value& value) {
-  SimdjsonValueType typed_value{};
-  simdjson::error_code error_code;
-  if constexpr (std::is_same_v<SimdjsonValueType, SimdjsonNull>) {
-    // simdjson has no get<>() for null; probe it explicitly
-    bool is_null;
-    error_code = value.is_null().get(is_null);
-    if (error_code == simdjson::SUCCESS && !is_null) {
-      error_code = simdjson::INCORRECT_TYPE;
-    }
-  } else {
-    error_code = value.get(typed_value);
-  }
-  if (error_code != simdjson::SUCCESS) {
-    sj::json_type json_type;
-    if (value.type().get(json_type) != simdjson::SUCCESS) {
-      return Status::Invalid("Expected ", JsonTypeName<SimdjsonValueType>(),
-                             " or null, got malformed JSON value");
-    }
-    return Status::Invalid("Expected ", JsonTypeName<SimdjsonValueType>(),
-                           " or null, got JSON type ", 
JsonTypeName(json_type));
-  }
-  return typed_value;
-}
-
-template <typename SimdjsonValueType>
-Result<SimdjsonValueType> GetJsonResult(
-    simdjson::simdjson_result<SimdjsonValueType> element, std::string_view 
error) {
-  SimdjsonValueType typed_value;
-  if (auto error_code = std::move(element).get(typed_value);
-      error_code != simdjson::SUCCESS) {
-    return Status::Invalid(error, simdjson::error_message(error_code));
-  }
-  return typed_value;
-}
-
-// Result<bool> because peeking the nonRootScalar can fail (parsed lazily)
-Result<bool> IsJsonNull(sj::value& value) {
-  bool is_null;
-  if (auto error_code = value.is_null().get(is_null); error_code != 
simdjson::SUCCESS) {
-    return Status::Invalid("Error checking for JSON null: ",
-                           simdjson::error_message(error_code));
-  }
-  return is_null;
-}
-
 class JSONConverter {
  public:
   virtual ~JSONConverter() = default;
@@ -215,7 +106,7 @@ class ConcreteConverter : public JSONConverter {
     int32_t num_elements = 0;
     for (auto element : json_array) {
       ARROW_ASSIGN_OR_RAISE(auto value,
-                            GetJsonResult<sj::value>(
+                            internal::GetSimdjsonResult<sj::value>(
                                 element, "Could not iterate elements of JSON 
array: "));
       RETURN_NOT_OK(self->AppendValue(value));
       num_elements++;
@@ -251,7 +142,7 @@ class NullConverter final : public 
ConcreteConverter<NullConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_RETURN_NOT_OK(GetJsonAs<SimdjsonNull>(json_obj));
+    ARROW_RETURN_NOT_OK(internal::GetJsonAs<internal::SimdjsonNull>(json_obj));
     return AppendNull();
   }
 
@@ -272,7 +163,7 @@ class BooleanConverter final : public 
ConcreteConverter<BooleanConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return AppendNull();
     }
@@ -280,7 +171,7 @@ class BooleanConverter final : public 
ConcreteConverter<BooleanConverter> {
     if (json_obj.get(int_value) == simdjson::SUCCESS) {
       return builder_->Append(int_value != 0);
     }
-    ARROW_ASSIGN_OR_RAISE(bool bool_value, GetJsonAs<bool>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool bool_value, 
internal::GetJsonAs<bool>(json_obj));
     return builder_->Append(bool_value);
   }
 
@@ -299,7 +190,7 @@ enable_if_physical_signed_integer<T, Status> 
ConvertNumber(sj::value& json_obj,
                                                            const DataType& 
type,
                                                            typename T::c_type* 
out) {
   *out = static_cast<typename T::c_type>(0);
-  ARROW_ASSIGN_OR_RAISE(int64_t v64, GetJsonAs<int64_t>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(int64_t v64, internal::GetJsonAs<int64_t>(json_obj));
   *out = static_cast<typename T::c_type>(v64);
   if (*out == v64) {
     return Status::OK();
@@ -314,7 +205,7 @@ enable_if_unsigned_integer<T, Status> 
ConvertNumber(sj::value& json_obj,
                                                     const DataType& type,
                                                     typename T::c_type* out) {
   *out = static_cast<typename T::c_type>(0);
-  ARROW_ASSIGN_OR_RAISE(uint64_t v64, GetJsonAs<uint64_t>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(uint64_t v64, internal::GetJsonAs<uint64_t>(json_obj));
   *out = static_cast<typename T::c_type>(v64);
   if (*out == v64) {
     return Status::OK();
@@ -356,7 +247,7 @@ enable_if_half_float<T, Status> ConvertNumber(sj::value& 
json_obj, const DataTyp
     *out = Float16(f64.value()).bits();
     return Status::OK();
   }
-  ARROW_ASSIGN_OR_RAISE(auto f64, GetJsonAs<double>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(auto f64, internal::GetJsonAs<double>(json_obj));
   *out = Float16(f64).bits();
   return arrow::Status::OK();
 }
@@ -371,7 +262,7 @@ enable_if_physical_floating_point<T, Status> 
ConvertNumber(sj::value& json_obj,
     *out = static_cast<typename T::c_type>(f64.value());
     return Status::OK();
   }
-  ARROW_ASSIGN_OR_RAISE(auto f64, GetJsonAs<double>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(auto f64, internal::GetJsonAs<double>(json_obj));
   *out = static_cast<typename T::c_type>(f64);
   return arrow::Status::OK();
 }
@@ -395,9 +286,9 @@ Status ProcessJsonArrayElements(
                              " elements, had ", index);
     }
 
-    ARROW_ASSIGN_OR_RAISE(
-        sj::value element,
-        GetJsonResult<sj::value>(*it, "Could not iterate elements of JSON 
array: "));
+    ARROW_ASSIGN_OR_RAISE(sj::value element,
+                          internal::GetSimdjsonResult<sj::value>(
+                              *it, "Could not iterate elements of JSON array: 
"));
     RETURN_NOT_OK(handler(element));
     ++it;
     ++index;
@@ -426,7 +317,7 @@ class IntegerConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
@@ -454,7 +345,7 @@ class FloatConverter final : public 
ConcreteConverter<FloatConverter<Type, Build
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
@@ -485,11 +376,12 @@ class DecimalConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
-    ARROW_ASSIGN_OR_RAISE(auto string_value, 
GetJsonAs<std::string_view>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto string_value,
+                          internal::GetJsonAs<std::string_view>(json_obj));
     int32_t precision, scale;
     DecimalValue d;
     RETURN_NOT_OK(DecimalValue::FromString(string_value, &d, &precision, 
&scale));
@@ -528,7 +420,7 @@ class TimestampConverter final : public 
ConcreteConverter<TimestampConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
@@ -563,12 +455,12 @@ class DayTimeIntervalConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
 
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
 
     DayTimeIntervalType::DayMilliseconds value;
     RETURN_NOT_OK(ProcessJsonArrayElements<2>(
@@ -597,12 +489,12 @@ class MonthDayNanoIntervalConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
 
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
 
     MonthDayNanoIntervalType::MonthDayNanos value;
     RETURN_NOT_OK(ProcessJsonArrayElements<3>(
@@ -637,12 +529,12 @@ class StringConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
 
-    ARROW_ASSIGN_OR_RAISE(auto view, GetJsonAs<std::string_view>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto view, 
internal::GetJsonAs<std::string_view>(json_obj));
     return builder_->Append(view);
   }
 
@@ -666,11 +558,11 @@ class FixedSizeBinaryConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
-    ARROW_ASSIGN_OR_RAISE(auto view, GetJsonAs<std::string_view>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto view, 
internal::GetJsonAs<std::string_view>(json_obj));
     if (view.length() != static_cast<size_t>(builder_->byte_width())) {
       std::stringstream ss;
       ss << "Invalid string length " << view.length() << " in JSON input for "
@@ -710,11 +602,11 @@ class VarLengthListLikeConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
     size_t num_elements;
     if (array.count_elements().get(num_elements) != simdjson::SUCCESS) {
       return Status::Invalid("Malformed JSON array for type ", 
this->type_->ToString());
@@ -750,24 +642,25 @@ class MapConverter final : public 
ConcreteConverter<MapConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
     RETURN_NOT_OK(builder_->Append());
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
 
     for (auto json_pair_result : array) {
       ARROW_ASSIGN_OR_RAISE(
           auto json_pair,
-          GetJsonResult<sj::value>(json_pair_result,
-                                   "Could not iterate elements of JSON array: 
"));
-      ARROW_ASSIGN_OR_RAISE(auto json_pair_array, 
GetJsonAs<sj::array>(json_pair));
+          internal::GetSimdjsonResult<sj::value>(
+              json_pair_result, "Could not iterate elements of JSON array: "));
+      ARROW_ASSIGN_OR_RAISE(auto json_pair_array,
+                            internal::GetJsonAs<sj::array>(json_pair));
 
       RETURN_NOT_OK(ProcessJsonArrayElements<2>(
           json_pair_array, "key-item pair",
           {[this](sj::value& key) {
-             ARROW_ASSIGN_OR_RAISE(bool key_is_null, IsJsonNull(key));
+             ARROW_ASSIGN_OR_RAISE(bool key_is_null, 
internal::IsJsonNull(key));
              if (key_is_null) {
                return Status::Invalid("null key is invalid");
              }
@@ -803,13 +696,13 @@ class FixedSizeListConverter final : public 
ConcreteConverter<FixedSizeListConve
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
     RETURN_NOT_OK(builder_->Append());
     // Extend the child converter with this JSON array
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
     ARROW_ASSIGN_OR_RAISE(int32_t size, child_converter_->AppendValues(array));
     if (size != list_size_) {
       return Status::Invalid("incorrect list size ", size);
@@ -852,7 +745,7 @@ class StructConverter final : public 
ConcreteConverter<StructConverter> {
   // or an object mapping struct names to values (omitted struct members
   // are mapped to null).
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
@@ -870,14 +763,14 @@ class StructConverter final : public 
ConcreteConverter<StructConverter> {
       size_t i = 0;
       for (auto child : array) {
         ARROW_ASSIGN_OR_RAISE(auto child_value,
-                              GetJsonResult<sj::value>(
+                              internal::GetSimdjsonResult<sj::value>(
                                   child, "Could not iterate elements of JSON 
array: "));
         RETURN_NOT_OK(child_converters_[i]->AppendValue(child_value));
         ++i;
       }
       return builder_->Append();
     }
-    ARROW_ASSIGN_OR_RAISE(auto object, GetJsonAs<sj::object>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto object, 
internal::GetJsonAs<sj::object>(json_obj));
     // Iterate the object fields in JSON order (the on-demand API is
     // forward-only, so per-field lookups would be quadratic and would also
     // compare against raw, still-escaped keys). Fields absent from the JSON
@@ -885,9 +778,9 @@ class StructConverter final : public 
ConcreteConverter<StructConverter> {
     auto num_fields = type_->num_fields();
     std::vector<bool> field_seen(num_fields, false);
     for (auto field_result : object) {
-      ARROW_ASSIGN_OR_RAISE(
-          auto field,
-          GetJsonResult<sj::field>(field_result, "Error getting field of 
object: "));
+      ARROW_ASSIGN_OR_RAISE(auto field,
+                            internal::GetSimdjsonResult<sj::field>(
+                                field_result, "Error getting field of object: 
"));
       std::string_view key;
       if (field.unescaped_key(/*allow_replacement=*/false).get(key) !=
           simdjson::SUCCESS) {
@@ -961,12 +854,12 @@ class UnionConverter final : public 
ConcreteConverter<UnionConverter> {
   // Append a JSON value that must be a 2-long array, containing the type_id
   // and value of the UnionArray's slot.
   Status AppendValue(sj::value& json_obj) override {
-    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    ARROW_ASSIGN_OR_RAISE(bool is_null, internal::IsJsonNull(json_obj));
     if (is_null) {
       return this->AppendNull();
     }
 
-    ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+    ARROW_ASSIGN_OR_RAISE(auto array, 
internal::GetJsonAs<sj::array>(json_obj));
 
     int8_t id = 0;
     std::shared_ptr<JSONConverter> child_converter;
@@ -974,7 +867,7 @@ class UnionConverter final : public 
ConcreteConverter<UnionConverter> {
     RETURN_NOT_OK(ProcessJsonArrayElements<2>(
         array, "[type_id, value] pair",
         {[this, &id, &child_converter](sj::value& id_elem) {
-           ARROW_ASSIGN_OR_RAISE(auto id_value, GetJsonAs<int64_t>(id_elem));
+           ARROW_ASSIGN_OR_RAISE(auto id_value, 
internal::GetJsonAs<int64_t>(id_elem));
            id = static_cast<int8_t>(id_value);
            auto child_num = type_id_to_child_num_[id];
            if (child_num == -1) {
@@ -1156,7 +1049,7 @@ Result<std::shared_ptr<Array>> ArrayFromJSONString(const 
std::shared_ptr<DataTyp
       error_code != simdjson::SUCCESS) {
     return Status::Invalid("JSON parse error: ", 
simdjson::error_message(error_code));
   }
-  ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(auto array, internal::GetJsonAs<sj::array>(json_obj));
 
   // The JSON document should be an array, append it
   RETURN_NOT_OK(converter->AppendValues(array));
@@ -1226,7 +1119,7 @@ Result<std::shared_ptr<Scalar>> ScalarFromJSONString(
       error_code != simdjson::SUCCESS) {
     return Status::Invalid("JSON parse error: ", 
simdjson::error_message(error_code));
   }
-  ARROW_ASSIGN_OR_RAISE(auto singleton_array, GetJsonAs<sj::array>(json_obj));
+  ARROW_ASSIGN_OR_RAISE(auto singleton_array, 
internal::GetJsonAs<sj::array>(json_obj));
 
   ARROW_ASSIGN_OR_RAISE(int32_t num_elements, 
converter->AppendValues(singleton_array));
   if (num_elements != 1) {
diff --git a/cpp/src/arrow/json/json_writer_internal.cc 
b/cpp/src/arrow/json/json_writer_internal.cc
index 446c7f06f4..5676949024 100644
--- a/cpp/src/arrow/json/json_writer_internal.cc
+++ b/cpp/src/arrow/json/json_writer_internal.cc
@@ -16,9 +16,12 @@
 // 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();
@@ -96,6 +99,85 @@ void JsonWriter::Double(double 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::GetSimdjsonResult(field.unescaped_key(),
+                                                    "Failed to get object key: 
"));
+
+          Key(key);
+
+          ARROW_ASSIGN_OR_RAISE(
+              auto field_value,
+              internal::GetSimdjsonResult(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::GetSimdjsonResult(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::GetSimdjsonResult(simdjson::to_json_string(value),
+                                                          "Failed to get raw 
JSON: "));
+        RawValue(raw_json);
+        return Status::OK();
+      });
+}
+
 void JsonWriter::Null() {
   MaybeComma();
   builder_.append_null();
diff --git a/cpp/src/arrow/json/json_writer_internal.h 
b/cpp/src/arrow/json/json_writer_internal.h
index e6043bd7e5..4a8200f1df 100644
--- a/cpp/src/arrow/json/json_writer_internal.h
+++ b/cpp/src/arrow/json/json_writer_internal.h
@@ -23,6 +23,7 @@
 #include <string_view>
 
 #include "arrow/result.h"
+#include "arrow/status.h"
 #include "arrow/util/visibility.h"
 
 namespace arrow::json {
@@ -51,6 +52,8 @@ class ARROW_EXPORT JsonWriter {
 
   void Double(double value);
 
+  Status WriteValue(simdjson::ondemand::value value);
+
   void Null();
 
   void StringField(std::string_view key, std::string_view value);
diff --git a/cpp/src/arrow/json/json_writer_internal_test.cc 
b/cpp/src/arrow/json/json_writer_internal_test.cc
index abdf9be7af..7fc12e8adb 100644
--- a/cpp/src/arrow/json/json_writer_internal_test.cc
+++ b/cpp/src/arrow/json/json_writer_internal_test.cc
@@ -20,6 +20,8 @@
 #include "arrow/json/json_writer_internal.h"
 #include "arrow/testing/gtest_util.h"
 
+namespace sj = simdjson::ondemand;
+
 namespace arrow::json {
 
 TEST(JsonWriter, SimpleObject) {
@@ -170,4 +172,122 @@ TEST(JsonWriter, StringWithExplicitLength) {
   EXPECT_EQ(json, R"({"value":"abc"})");
 }
 
+TEST(JsonWriter, WriteValueSimpleObject) {
+  sj::parser parser;
+  std::string json_str = R"({"a":42,"b":"hello"})";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(out, R"({"a":42,"b":"hello"})");
+}
+
+TEST(JsonWriter, WriteValueNestedObject) {
+  sj::parser parser;
+  std::string json_str = R"({"child":{"x":true}})";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(out, R"({"child":{"x":true}})");
+}
+
+TEST(JsonWriter, WriteValueObjectWithArray) {
+  sj::parser parser;
+  std::string json_str = R"({"values":[1,2,3]})";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(out, R"({"values":[1,2,3]})");
+}
+
+TEST(JsonWriter, WriteValueComplexObject) {
+  sj::parser parser;
+  std::string json_str =
+      
R"({"name":"arrow","version":1,"enabled":true,"values":[1,2.5,null,{"nested":[false,{"x":10}]}]})";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(
+      out,
+      
R"({"name":"arrow","version":1,"enabled":true,"values":[1,2.5,null,{"nested":[false,{"x":10}]}]})");
+}
+
+TEST(JsonWriter, WriteValueEmptyObject) {
+  sj::parser parser;
+  std::string json_str = "{}";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(out, "{}");
+}
+
+TEST(JsonWriter, WriteValueAllNumberTypes) {
+  sj::parser parser;
+  std::string json_str = R"({
+    "signed":-42,
+    "unsigned":18446744073709551615,
+    "double":2.5,
+    "big":184467440737095516161234567890
+  })";
+  simdjson::padded_string json(json_str);
+
+  sj::document doc;
+  ASSERT_EQ(parser.iterate(json).get(doc), simdjson::SUCCESS);
+
+  sj::value value;
+  ASSERT_EQ(doc.get_value().get(value), simdjson::SUCCESS);
+
+  JsonWriter writer;
+  ASSERT_OK(writer.WriteValue(value));
+
+  ASSERT_OK_AND_ASSIGN(std::string_view out, writer.GetString());
+  EXPECT_EQ(
+      out,
+      
R"({"signed":-42,"unsigned":18446744073709551615,"double":2.5,"big":184467440737095516161234567890})");
+}
+
 }  // namespace arrow::json
diff --git a/cpp/src/arrow/util/simdjson_internal.h 
b/cpp/src/arrow/util/simdjson_internal.h
new file mode 100644
index 0000000000..8ffb741da4
--- /dev/null
+++ b/cpp/src/arrow/util/simdjson_internal.h
@@ -0,0 +1,244 @@
+// 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 <concepts>
+#include <cstdint>
+#include <string_view>
+#include <utility>
+
+#include <simdjson.h>
+
+#include "arrow/result.h"
+#include "arrow/status.h"
+
+namespace arrow {
+namespace internal {
+
+// Empty struct to represent the type of a simdjson null value
+struct SimdjsonNull {};
+
+template <typename T>
+struct JsonTypeNameOf;
+
+template <>
+struct JsonTypeNameOf<simdjson::ondemand::array> {
+  static constexpr const char* kValue = "array";
+};
+
+template <>
+struct JsonTypeNameOf<simdjson::ondemand::object> {
+  static constexpr const char* kValue = "object";
+};
+
+template <>
+struct JsonTypeNameOf<std::string_view> {
+  static constexpr const char* kValue = "string";
+};
+
+template <>
+struct JsonTypeNameOf<bool> {
+  static constexpr const char* kValue = "boolean";
+};
+
+template <>
+struct JsonTypeNameOf<SimdjsonNull> {
+  static constexpr const char* kValue = "null";
+};
+
+template <>
+struct JsonTypeNameOf<int64_t> {
+  static constexpr const char* kValue = "number";
+};
+
+template <>
+struct JsonTypeNameOf<uint64_t> {
+  static constexpr const char* kValue = "number";
+};
+
+template <>
+struct JsonTypeNameOf<double> {
+  static constexpr const char* kValue = "number";
+};
+
+template <typename T>
+constexpr const char* JsonTypeName() {
+  return JsonTypeNameOf<T>::kValue;
+}
+
+template <typename T>
+Result<T> GetSimdjsonResult(simdjson::simdjson_result<T> result, 
std::string_view error) {
+  T value;
+  if (auto error_code = std::move(result).get(value); error_code != 
simdjson::SUCCESS) {
+    return Status::Invalid(error, simdjson::error_message(error_code));
+  }
+  return value;
+}
+
+template <typename ObjectFn, typename ArrayFn, typename StringFn, typename 
BoolFn,
+          typename NullFn, typename Int64Fn, typename Uint64Fn, typename 
DoubleFn,
+          typename BigIntegerFn>
+Status VisitJsonValue(simdjson::ondemand::value value, ObjectFn&& object_fn,
+                      ArrayFn&& array_fn, StringFn&& string_fn, BoolFn&& 
bool_fn,
+                      NullFn&& null_fn, Int64Fn&& int64_fn, Uint64Fn&& 
uint64_fn,
+                      DoubleFn&& double_fn, BigIntegerFn&& big_integer_fn) {
+  ARROW_ASSIGN_OR_RAISE(
+      auto type, GetSimdjsonResult(value.type(), "Failed to determine JSON 
type: "));
+
+  switch (type) {
+    case simdjson::ondemand::json_type::object: {
+      ARROW_ASSIGN_OR_RAISE(
+          auto object,
+          GetSimdjsonResult(value.get_object(), "Failed to get JSON object: 
"));
+      return object_fn(object);
+    }
+
+    case simdjson::ondemand::json_type::array: {
+      ARROW_ASSIGN_OR_RAISE(
+          auto array, GetSimdjsonResult(value.get_array(), "Failed to get JSON 
array: "));
+      return array_fn(array);
+    }
+
+    case simdjson::ondemand::json_type::string: {
+      ARROW_ASSIGN_OR_RAISE(
+          auto string,
+          GetSimdjsonResult(value.get_string(), "Failed to get JSON string: 
"));
+      return string_fn(string);
+    }
+
+    case simdjson::ondemand::json_type::boolean: {
+      ARROW_ASSIGN_OR_RAISE(
+          auto boolean,
+          GetSimdjsonResult(value.get_bool(), "Failed to get JSON boolean: "));
+      return bool_fn(boolean);
+    }
+
+    case simdjson::ondemand::json_type::null:
+      return null_fn();
+
+    case simdjson::ondemand::json_type::number: {
+      ARROW_ASSIGN_OR_RAISE(auto number_type,
+                            GetSimdjsonResult(value.get_number_type(),
+                                              "Failed to determine JSON number 
type: "));
+
+      switch (number_type) {
+        case simdjson::ondemand::number_type::signed_integer: {
+          ARROW_ASSIGN_OR_RAISE(
+              auto number,
+              GetSimdjsonResult(value.get_int64(), "Failed to get signed 
integer: "));
+          return int64_fn(number);
+        }
+
+        case simdjson::ondemand::number_type::unsigned_integer: {
+          ARROW_ASSIGN_OR_RAISE(
+              auto number,
+              GetSimdjsonResult(value.get_uint64(), "Failed to get unsigned 
integer: "));
+          return uint64_fn(number);
+        }
+
+        case simdjson::ondemand::number_type::floating_point_number: {
+          ARROW_ASSIGN_OR_RAISE(
+              auto number, GetSimdjsonResult(value.get_double(),
+                                             "Failed to get floating-point 
number: "));
+          return double_fn(number);
+        }
+
+        case simdjson::ondemand::number_type::big_integer:
+          return big_integer_fn(value);
+      }
+
+      return Status::Invalid("Unknown JSON number type");
+    }
+
+    case simdjson::ondemand::json_type::unknown:
+      return Status::Invalid("Unknown JSON type");
+  }
+
+  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";
+  }
+}
+
+// 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;
+}
+
+template <typename SimdjsonValueType>
+Result<SimdjsonValueType> GetJsonAs(simdjson::ondemand::value& value) {
+  SimdjsonValueType typed_value{};
+  simdjson::error_code error_code;
+
+  if constexpr (std::same_as<SimdjsonValueType, SimdjsonNull>) {
+    // simdjson has no get<>() for null; probe it explicitly
+    bool is_null;
+    error_code = value.is_null().get(is_null);
+    if (error_code == simdjson::SUCCESS && !is_null) {
+      error_code = simdjson::INCORRECT_TYPE;
+    }
+  } else {
+    error_code = value.get(typed_value);
+  }
+
+  if (error_code != simdjson::SUCCESS) {
+    simdjson::ondemand::json_type json_type;
+    if (value.type().get(json_type) != simdjson::SUCCESS) {
+      if constexpr (std::same_as<SimdjsonValueType, SimdjsonNull>) {
+        return Status::Invalid("Expected null, got malformed JSON value");
+      } else {
+        return Status::Invalid("Expected ", JsonTypeName<SimdjsonValueType>(),
+                               ", got malformed JSON value");
+      }
+    }
+
+    if constexpr (std::same_as<SimdjsonValueType, SimdjsonNull>) {
+      return Status::Invalid("Expected null, got JSON type ", 
JsonTypeName(json_type));
+    } else {
+      return Status::Invalid("Expected ", JsonTypeName<SimdjsonValueType>(),
+                             ", got JSON type ", JsonTypeName(json_type));
+    }
+  }
+
+  return typed_value;
+}
+
+}  // namespace internal
+}  // namespace arrow

Reply via email to