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 86f8d42b68 GH-50730: [C++] Do not use throwing <simdjson> api (#50732)
86f8d42b68 is described below

commit 86f8d42b68f6b4624cf544aec23fe9cba1346431
Author: Alexander Taepper <[email protected]>
AuthorDate: Thu Jul 30 11:50:47 2026 +0200

    GH-50730: [C++] Do not use throwing <simdjson> api (#50732)
    
    ### Rationale for this change
    
    The simdjson API has a throwing and non-throwing subset. 
https://github.com/apache/arrow/pull/50672 activated a compiler flag that 
disables the throwing subset of the API, which broke some CI builds.
    
    ### What changes are included in this PR?
    
    This changes `json_write_internal.cc` and `from_string.cc` to use the 
non-throwing simdjson api
    
    ### Are these changes tested?
    
    Yes
    
    ### Are there any user-facing changes?
    
    No
    
    * GitHub Issue: #50730
    
    Authored-by: Alexander Taepper <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/arrow/extension/fixed_shape_tensor.cc      |  5 +-
 cpp/src/arrow/extension/opaque.cc                  |  5 +-
 cpp/src/arrow/extension/variable_shape_tensor.cc   |  5 +-
 cpp/src/arrow/integration/json_integration.cc      |  3 +-
 cpp/src/arrow/integration/json_integration_test.cc |  4 +-
 cpp/src/arrow/json/from_string.cc                  | 55 ++++++++++++++++------
 cpp/src/arrow/json/json_writer_internal.cc         | 13 ++++-
 cpp/src/arrow/json/json_writer_internal.h          |  3 +-
 cpp/src/arrow/json/json_writer_internal_test.cc    | 41 ++++++++++++----
 .../encryption/file_system_key_material_store.cc   |  3 +-
 cpp/src/parquet/encryption/key_material.cc         |  5 +-
 cpp/src/parquet/encryption/key_metadata.cc         |  5 +-
 .../parquet/encryption/local_wrap_kms_client.cc    |  5 +-
 13 files changed, 115 insertions(+), 37 deletions(-)

diff --git a/cpp/src/arrow/extension/fixed_shape_tensor.cc 
b/cpp/src/arrow/extension/fixed_shape_tensor.cc
index 697bab92b3..cd3d783479 100644
--- a/cpp/src/arrow/extension/fixed_shape_tensor.cc
+++ b/cpp/src/arrow/extension/fixed_shape_tensor.cc
@@ -104,7 +104,10 @@ std::string FixedShapeTensorType::Serialize() const {
 
   writer.EndObject();
 
-  return std::string(writer.GetString());
+  Result<std::string_view> json = writer.GetString();
+  // can only fail in OutOfMemory scenarios
+  ARROW_CHECK_OK(json.status());
+  return std::string(*json);
 }
 
 Result<std::shared_ptr<DataType>> FixedShapeTensorType::Deserialize(
diff --git a/cpp/src/arrow/extension/opaque.cc 
b/cpp/src/arrow/extension/opaque.cc
index 5fe904fce6..b1ad48fdba 100644
--- a/cpp/src/arrow/extension/opaque.cc
+++ b/cpp/src/arrow/extension/opaque.cc
@@ -57,7 +57,10 @@ std::string OpaqueType::Serialize() const {
 
   writer.EndObject();
 
-  return std::string(writer.GetString());
+  Result<std::string_view> json = writer.GetString();
+  // can only fail in OutOfMemory scenarios
+  ARROW_CHECK_OK(json.status());
+  return std::string(*json);
 }
 
 Result<std::shared_ptr<DataType>> OpaqueType::Deserialize(
diff --git a/cpp/src/arrow/extension/variable_shape_tensor.cc 
b/cpp/src/arrow/extension/variable_shape_tensor.cc
index a67bd6dea8..40171f909a 100644
--- a/cpp/src/arrow/extension/variable_shape_tensor.cc
+++ b/cpp/src/arrow/extension/variable_shape_tensor.cc
@@ -120,7 +120,10 @@ std::string VariableShapeTensorType::Serialize() const {
 
   writer.EndObject();
 
-  return std::string(writer.GetString());
+  Result<std::string_view> json = writer.GetString();
+  // can only fail in OutOfMemory scenarios
+  ARROW_CHECK_OK(json.status());
+  return std::string(*json);
 }
 
 Result<std::shared_ptr<DataType>> VariableShapeTensorType::Deserialize(
diff --git a/cpp/src/arrow/integration/json_integration.cc 
b/cpp/src/arrow/integration/json_integration.cc
index d7c0fa59e4..f4c3333664 100644
--- a/cpp/src/arrow/integration/json_integration.cc
+++ b/cpp/src/arrow/integration/json_integration.cc
@@ -78,7 +78,8 @@ class IntegrationJsonWriter::Impl {
     writer_.EndArray();  // Record batches
     writer_.EndObject();
 
-    return std::string(writer_.GetString());
+    ARROW_ASSIGN_OR_RAISE(std::string_view json, writer_.GetString());
+    return std::string(json);
   }
 
   Status WriteRecordBatch(const RecordBatch& batch) {
diff --git a/cpp/src/arrow/integration/json_integration_test.cc 
b/cpp/src/arrow/integration/json_integration_test.cc
index e7a55d13e0..477ef932d2 100644
--- a/cpp/src/arrow/integration/json_integration_test.cc
+++ b/cpp/src/arrow/integration/json_integration_test.cc
@@ -732,7 +732,7 @@ void TestSchemaRoundTrip(const std::shared_ptr<Schema>& 
schema) {
   ASSERT_OK(json::WriteSchema(*schema, mapper, &writer));
   writer.EndObject();
 
-  std::string json_schema(writer.GetString());
+  ASSERT_OK_AND_ASSIGN(std::string_view json_schema, writer.GetString());
 
   rj::Document d;
   // Pass explicit size to avoid ASAN issues with
@@ -752,7 +752,7 @@ void TestArrayRoundTrip(const Array& array) {
 
   ASSERT_OK(json::WriteArray(name, array, &writer));
 
-  std::string array_as_json(writer.GetString());
+  ASSERT_OK_AND_ASSIGN(std::string_view array_as_json, writer.GetString());
 
   rj::Document d;
   // Pass explicit size to avoid ASAN issues with
diff --git a/cpp/src/arrow/json/from_string.cc 
b/cpp/src/arrow/json/from_string.cc
index 9cb14a92a6..5694fbde4c 100644
--- a/cpp/src/arrow/json/from_string.cc
+++ b/cpp/src/arrow/json/from_string.cc
@@ -166,6 +166,16 @@ Result<SimdjsonValueType> GetJsonResult(
   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;
@@ -262,7 +272,8 @@ class BooleanConverter final : public 
ConcreteConverter<BooleanConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return AppendNull();
     }
     int64_t int_value;
@@ -415,7 +426,8 @@ class IntegerConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     c_type value;
@@ -442,7 +454,8 @@ class FloatConverter final : public 
ConcreteConverter<FloatConverter<Type, Build
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     c_type value;
@@ -472,7 +485,8 @@ class DecimalConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     ARROW_ASSIGN_OR_RAISE(auto string_value, 
GetJsonAs<std::string_view>(json_obj));
@@ -514,7 +528,8 @@ class TimestampConverter final : public 
ConcreteConverter<TimestampConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     int64_t value;
@@ -548,7 +563,8 @@ class DayTimeIntervalConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
 
@@ -581,7 +597,8 @@ class MonthDayNanoIntervalConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
 
@@ -620,7 +637,8 @@ class StringConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
 
@@ -648,7 +666,8 @@ class FixedSizeBinaryConverter final
   Status Init() override { return this->MakeConcreteBuilder(&builder_); }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     ARROW_ASSIGN_OR_RAISE(auto view, GetJsonAs<std::string_view>(json_obj));
@@ -691,7 +710,8 @@ class VarLengthListLikeConverter final
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     ARROW_ASSIGN_OR_RAISE(auto array, GetJsonAs<sj::array>(json_obj));
@@ -730,7 +750,8 @@ class MapConverter final : public 
ConcreteConverter<MapConverter> {
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     RETURN_NOT_OK(builder_->Append());
@@ -746,7 +767,8 @@ class MapConverter final : public 
ConcreteConverter<MapConverter> {
       RETURN_NOT_OK(ProcessJsonArrayElements<2>(
           json_pair_array, "key-item pair",
           {[this](sj::value& key) {
-             if (key.is_null()) {
+             ARROW_ASSIGN_OR_RAISE(bool key_is_null, IsJsonNull(key));
+             if (key_is_null) {
                return Status::Invalid("null key is invalid");
              }
              return key_converter_->AppendValue(key);
@@ -781,7 +803,8 @@ class FixedSizeListConverter final : public 
ConcreteConverter<FixedSizeListConve
   }
 
   Status AppendValue(sj::value& json_obj) override {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     RETURN_NOT_OK(builder_->Append());
@@ -829,7 +852,8 @@ 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 {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
     sj::array array;
@@ -937,7 +961,8 @@ 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 {
-    if (json_obj.is_null()) {
+    ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(json_obj));
+    if (is_null) {
       return this->AppendNull();
     }
 
diff --git a/cpp/src/arrow/json/json_writer_internal.cc 
b/cpp/src/arrow/json/json_writer_internal.cc
index 49038f26b0..446c7f06f4 100644
--- a/cpp/src/arrow/json/json_writer_internal.cc
+++ b/cpp/src/arrow/json/json_writer_internal.cc
@@ -102,7 +102,18 @@ void JsonWriter::Null() {
   needs_comma_ = true;
 }
 
-std::string_view JsonWriter::GetString() const { return 
builder_.view().value(); }
+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;
+}
 
 void JsonWriter::Clear() {
   builder_.clear();
diff --git a/cpp/src/arrow/json/json_writer_internal.h 
b/cpp/src/arrow/json/json_writer_internal.h
index 4173cb4c51..e6043bd7e5 100644
--- a/cpp/src/arrow/json/json_writer_internal.h
+++ b/cpp/src/arrow/json/json_writer_internal.h
@@ -22,6 +22,7 @@
 #include <cstdint>
 #include <string_view>
 
+#include "arrow/result.h"
 #include "arrow/util/visibility.h"
 
 namespace arrow::json {
@@ -55,7 +56,7 @@ class ARROW_EXPORT JsonWriter {
   void StringField(std::string_view key, std::string_view value);
   void BoolField(std::string_view key, bool value);
 
-  std::string_view GetString() const;
+  Result<std::string_view> GetString() const;
 
   void Clear();
 
diff --git a/cpp/src/arrow/json/json_writer_internal_test.cc 
b/cpp/src/arrow/json/json_writer_internal_test.cc
index 01e6d7ecb8..abdf9be7af 100644
--- a/cpp/src/arrow/json/json_writer_internal_test.cc
+++ b/cpp/src/arrow/json/json_writer_internal_test.cc
@@ -18,6 +18,7 @@
 #include <gtest/gtest.h>
 
 #include "arrow/json/json_writer_internal.h"
+#include "arrow/testing/gtest_util.h"
 
 namespace arrow::json {
 
@@ -31,7 +32,9 @@ TEST(JsonWriter, SimpleObject) {
   writer.String("hello");
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"a":42,"b":"hello"})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"a":42,"b":"hello"})");
 }
 
 TEST(JsonWriter, Array) {
@@ -43,7 +46,9 @@ TEST(JsonWriter, Array) {
   writer.Int(3);
   writer.EndArray();
 
-  EXPECT_EQ(writer.GetString(), "[1,2,3]");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, "[1,2,3]");
 }
 
 TEST(JsonWriter, NestedObject) {
@@ -59,7 +64,9 @@ TEST(JsonWriter, NestedObject) {
 
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"child":{"x":true}})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"child":{"x":true}})");
 }
 
 TEST(JsonWriter, NullValue) {
@@ -70,7 +77,9 @@ TEST(JsonWriter, NullValue) {
   writer.Null();
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"value":null})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"value":null})");
 }
 
 TEST(JsonWriter, DoubleValue) {
@@ -81,7 +90,9 @@ TEST(JsonWriter, DoubleValue) {
   writer.Double(3.14);
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"pi":3.14})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"pi":3.14})");
 }
 
 TEST(JsonWriter, UnsignedValues) {
@@ -94,7 +105,9 @@ TEST(JsonWriter, UnsignedValues) {
   writer.Uint64(1234567890123ULL);
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"u32":42,"u64":1234567890123})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"u32":42,"u64":1234567890123})");
 }
 
 TEST(JsonWriter, Int64Value) {
@@ -105,7 +118,9 @@ TEST(JsonWriter, Int64Value) {
   writer.Int64(-1234567890123LL);
   writer.EndObject();
 
-  EXPECT_EQ(writer.GetString(), R"({"i64":-1234567890123})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"i64":-1234567890123})");
 }
 
 TEST(JsonWriter, Clear) {
@@ -122,7 +137,9 @@ TEST(JsonWriter, Clear) {
   writer.Int(5);
   writer.EndArray();
 
-  EXPECT_EQ(writer.GetString(), "[5]");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, "[5]");
 }
 
 TEST(JsonWriter, RawValue) {
@@ -133,7 +150,9 @@ TEST(JsonWriter, RawValue) {
   writer.RawValue("123.456");
   writer.EndObject();
 
-  ASSERT_EQ(writer.GetString(), R"({"number":123.456})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"number":123.456})");
 }
 
 TEST(JsonWriter, StringWithExplicitLength) {
@@ -146,7 +165,9 @@ TEST(JsonWriter, StringWithExplicitLength) {
   writer.String(std::string_view(value, 3));
   writer.EndObject();
 
-  ASSERT_EQ(writer.GetString(), R"({"value":"abc"})");
+  ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString());
+
+  EXPECT_EQ(json, R"({"value":"abc"})");
 }
 
 }  // namespace arrow::json
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 e5b215a846..ece14b9664 100644
--- a/cpp/src/parquet/encryption/file_system_key_material_store.cc
+++ b/cpp/src/parquet/encryption/file_system_key_material_store.cc
@@ -87,7 +87,8 @@ std::string 
FileSystemKeyMaterialStore::BuildKeyMaterialMapJson() {
     writer.StringField(it.first, it.second);
   }
   writer.EndObject();
-  return std::string(writer.GetString());
+  PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString());
+  return std::string(json);
 }
 
 void FileSystemKeyMaterialStore::SaveMaterial() {
diff --git a/cpp/src/parquet/encryption/key_material.cc 
b/cpp/src/parquet/encryption/key_material.cc
index 67f838c1df..305ad59698 100644
--- a/cpp/src/parquet/encryption/key_material.cc
+++ b/cpp/src/parquet/encryption/key_material.cc
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <string_view>
+
 #include "arrow/json/json_writer_internal.h"
 #include "arrow/json/object_parser.h"
 
@@ -163,7 +165,8 @@ std::string KeyMaterial::SerializeToJson(
   }
 
   json_writer.EndObject();
-  return std::string(json_writer.GetString());
+  PARQUET_ASSIGN_OR_THROW(std::string_view json, json_writer.GetString());
+  return std::string(json);
 }
 
 }  // namespace parquet::encryption
diff --git a/cpp/src/parquet/encryption/key_metadata.cc 
b/cpp/src/parquet/encryption/key_metadata.cc
index ed6c62955c..94253c87e3 100644
--- a/cpp/src/parquet/encryption/key_metadata.cc
+++ b/cpp/src/parquet/encryption/key_metadata.cc
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <string_view>
+
 #include "arrow/json/json_writer_internal.h"
 #include "arrow/json/object_parser.h"
 
@@ -84,7 +86,8 @@ std::string KeyMetadata::CreateSerializedForExternalMaterial(
 
   json_writer.EndObject();
 
-  return std::string(json_writer.GetString());
+  PARQUET_ASSIGN_OR_THROW(std::string_view json, json_writer.GetString());
+  return std::string(json);
 }
 
 }  // namespace parquet::encryption
diff --git a/cpp/src/parquet/encryption/local_wrap_kms_client.cc 
b/cpp/src/parquet/encryption/local_wrap_kms_client.cc
index b2a6872af5..dcb6c49836 100644
--- a/cpp/src/parquet/encryption/local_wrap_kms_client.cc
+++ b/cpp/src/parquet/encryption/local_wrap_kms_client.cc
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <string_view>
+
 #include "arrow/json/json_writer_internal.h"
 #include "arrow/json/object_parser.h"
 #include "arrow/util/secure_string.h"
@@ -50,7 +52,8 @@ std::string 
LocalWrapKmsClient::LocalKeyWrap::CreateSerialized(
 
   json_writer.EndObject();
 
-  return std::string(json_writer.GetString());
+  PARQUET_ASSIGN_OR_THROW(std::string_view json, json_writer.GetString());
+  return std::string(json);
 }
 
 LocalWrapKmsClient::LocalKeyWrap LocalWrapKmsClient::LocalKeyWrap::Parse(

Reply via email to