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 c44eca6892 GH-50779: [C++][Parquet] Replace remaining RapidJSON usage
with simdjson (#50781)
c44eca6892 is described below
commit c44eca6892364e58489fe564f910c0e1c1c6cb6b
Author: Aaditya Srinivasan <[email protected]>
AuthorDate: Thu Aug 13 14:42:02 2026 +0530
GH-50779: [C++][Parquet] Replace remaining RapidJSON usage with simdjson
(#50781)
### Rationale for this change
This PR continues the simdjson migration by replacing the remaining
RapidJSON usage under `cpp/src/parquet` with simdjson and `JsonWriter`. It also
updates the Meson build to support simdjson and removes unnecessary RapidJSON
dependencies from the Parquet build configuration.
### What changes are included in this PR?
- Replace the remaining RapidJSON parsing logic in `reader_test.cc` with
simdjson.
- Replace RapidJSON parsing and serialization in
`geospatial/util_json_internal.cc` with simdjson and `JsonWriter`.
- Replace RapidJSON string escaping in `types.cc` with `JsonWriter`.
- Add Meson support for simdjson, including a fallback CMake subproject.
- Link simdjson in the relevant Meson targets.
- Remove unnecessary RapidJSON dependencies from the Parquet Meson and
CMake build configuration.
* GitHub Issue: #50779
Authored-by: Aaditya Srinivasan <[email protected]>
Signed-off-by: Sutou Kouhei <[email protected]>
---
cpp/src/arrow/json/from_string.cc | 10 +-
cpp/src/arrow/json/json_writer_internal.cc | 18 +-
cpp/src/arrow/json/meson.build | 2 +-
cpp/src/arrow/meson.build | 18 +-
cpp/src/arrow/util/simdjson_internal.h | 120 ++++++++++--
cpp/src/parquet/CMakeLists.txt | 9 +-
cpp/src/parquet/geospatial/util_json_internal.cc | 213 ++++++++++++++-------
cpp/src/parquet/geospatial/util_json_internal.h | 8 +-
.../parquet/geospatial/util_json_internal_test.cc | 68 +++++++
cpp/src/parquet/meson.build | 4 +-
cpp/src/parquet/reader_test.cc | 22 +--
cpp/src/parquet/schema_test.cc | 6 +-
cpp/src/parquet/types.cc | 15 +-
cpp/subprojects/simdjson.wrap | 23 +++
14 files changed, 401 insertions(+), 135 deletions(-)
diff --git a/cpp/src/arrow/json/from_string.cc
b/cpp/src/arrow/json/from_string.cc
index c9d9106671..f573ae7106 100644
--- a/cpp/src/arrow/json/from_string.cc
+++ b/cpp/src/arrow/json/from_string.cc
@@ -106,7 +106,7 @@ class ConcreteConverter : public JSONConverter {
int32_t num_elements = 0;
for (auto element : json_array) {
ARROW_ASSIGN_OR_RAISE(auto value,
- internal::GetSimdjsonResult<sj::value>(
+ internal::ResolveSimdjsonResult<sj::value>(
element, "Could not iterate elements of JSON
array: "));
RETURN_NOT_OK(self->AppendValue(value));
num_elements++;
@@ -287,7 +287,7 @@ Status ProcessJsonArrayElements(
}
ARROW_ASSIGN_OR_RAISE(sj::value element,
- internal::GetSimdjsonResult<sj::value>(
+ internal::ResolveSimdjsonResult<sj::value>(
*it, "Could not iterate elements of JSON array:
"));
RETURN_NOT_OK(handler(element));
++it;
@@ -652,7 +652,7 @@ class MapConverter final : public
ConcreteConverter<MapConverter> {
for (auto json_pair_result : array) {
ARROW_ASSIGN_OR_RAISE(
auto json_pair,
- internal::GetSimdjsonResult<sj::value>(
+ internal::ResolveSimdjsonResult<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));
@@ -763,7 +763,7 @@ class StructConverter final : public
ConcreteConverter<StructConverter> {
size_t i = 0;
for (auto child : array) {
ARROW_ASSIGN_OR_RAISE(auto child_value,
- internal::GetSimdjsonResult<sj::value>(
+ internal::ResolveSimdjsonResult<sj::value>(
child, "Could not iterate elements of JSON
array: "));
RETURN_NOT_OK(child_converters_[i]->AppendValue(child_value));
++i;
@@ -779,7 +779,7 @@ class StructConverter final : public
ConcreteConverter<StructConverter> {
std::vector<bool> field_seen(num_fields, false);
for (auto field_result : object) {
ARROW_ASSIGN_OR_RAISE(auto field,
- internal::GetSimdjsonResult<sj::field>(
+ internal::ResolveSimdjsonResult<sj::field>(
field_result, "Error getting field of object:
"));
std::string_view key;
if (field.unescaped_key(/*allow_replacement=*/false).get(key) !=
diff --git a/cpp/src/arrow/json/json_writer_internal.cc
b/cpp/src/arrow/json/json_writer_internal.cc
index 5676949024..6d3e9ff9d8 100644
--- a/cpp/src/arrow/json/json_writer_internal.cc
+++ b/cpp/src/arrow/json/json_writer_internal.cc
@@ -108,14 +108,14 @@ Status JsonWriter::WriteValue(sj::value value) {
for (auto field : object) {
ARROW_ASSIGN_OR_RAISE(
- auto key, internal::GetSimdjsonResult(field.unescaped_key(),
- "Failed to get object key:
"));
+ auto key, internal::ResolveSimdjsonResult(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: "));
+ ARROW_ASSIGN_OR_RAISE(auto field_value,
+ internal::ResolveSimdjsonResult(
+ field.value(), "Failed to get object
value"));
RETURN_NOT_OK(WriteValue(field_value));
}
@@ -130,7 +130,7 @@ Status JsonWriter::WriteValue(sj::value value) {
for (auto element : array) {
ARROW_ASSIGN_OR_RAISE(
auto element_value,
- internal::GetSimdjsonResult(element, "Failed to iterate JSON
array: "));
+ internal::ResolveSimdjsonResult(element, "Failed to iterate JSON
array"));
RETURN_NOT_OK(WriteValue(element_value));
}
@@ -170,9 +170,9 @@ Status JsonWriter::WriteValue(sj::value value) {
},
[&](sj::value value) -> Status {
- ARROW_ASSIGN_OR_RAISE(auto raw_json,
-
internal::GetSimdjsonResult(simdjson::to_json_string(value),
- "Failed to get raw
JSON: "));
+ 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();
});
diff --git a/cpp/src/arrow/json/meson.build b/cpp/src/arrow/json/meson.build
index edf92a46fd..ee2a26b2cc 100644
--- a/cpp/src/arrow/json/meson.build
+++ b/cpp/src/arrow/json/meson.build
@@ -26,7 +26,7 @@ exc = executable(
'parser_test.cc',
'reader_test.cc',
],
- dependencies: [arrow_test_dep, rapidjson_dep],
+ dependencies: [arrow_test_dep, rapidjson_dep, simdjson_dep],
)
test('arrow-json-test', exc)
diff --git a/cpp/src/arrow/meson.build b/cpp/src/arrow/meson.build
index 955ef78973..b5443c28cf 100644
--- a/cpp/src/arrow/meson.build
+++ b/cpp/src/arrow/meson.build
@@ -318,6 +318,22 @@ 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()
@@ -520,7 +536,7 @@ if needs_json
'json/parser.cc',
'json/reader.cc',
],
- 'dependencies': [rapidjson_dep],
+ 'dependencies': [rapidjson_dep, simdjson_dep],
},
}
endif
diff --git a/cpp/src/arrow/util/simdjson_internal.h
b/cpp/src/arrow/util/simdjson_internal.h
index 8ffb741da4..1badd99938 100644
--- a/cpp/src/arrow/util/simdjson_internal.h
+++ b/cpp/src/arrow/util/simdjson_internal.h
@@ -82,10 +82,11 @@ constexpr const char* JsonTypeName() {
}
template <typename T>
-Result<T> GetSimdjsonResult(simdjson::simdjson_result<T> result,
std::string_view error) {
+Result<T> ResolveSimdjsonResult(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 Status::Invalid(error, ": ", simdjson::error_message(error_code));
}
return value;
}
@@ -98,33 +99,34 @@ Status VisitJsonValue(simdjson::ondemand::value value,
ObjectFn&& object_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: "));
+ auto type, ResolveSimdjsonResult(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:
"));
+ ResolveSimdjsonResult(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: "));
+ auto array,
+ ResolveSimdjsonResult(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:
"));
+ ResolveSimdjsonResult(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: "));
+ ResolveSimdjsonResult(value.get_bool(), "Failed to get JSON
boolean"));
return bool_fn(boolean);
}
@@ -132,29 +134,30 @@ Status VisitJsonValue(simdjson::ondemand::value value,
ObjectFn&& object_fn,
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: "));
+ ARROW_ASSIGN_OR_RAISE(
+ auto number_type,
+ ResolveSimdjsonResult(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: "));
+ ResolveSimdjsonResult(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: "));
+ ARROW_ASSIGN_OR_RAISE(auto number,
+ ResolveSimdjsonResult(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: "));
+ auto number, ResolveSimdjsonResult(value.get_double(),
+ "Failed to get floating-point
number"));
return double_fn(number);
}
@@ -240,5 +243,90 @@ Result<SimdjsonValueType>
GetJsonAs(simdjson::ondemand::value& value) {
return typed_value;
}
+template <typename T>
+Result<T> GetJsonField(simdjson::ondemand::object& object, std::string_view
key) {
+ for (auto field_result : object) {
+ ARROW_ASSIGN_OR_RAISE(
+ auto field, ResolveSimdjsonResult(field_result, "Failed to iterate
JSON object"));
+
+ ARROW_ASSIGN_OR_RAISE(
+ auto field_key,
+ ResolveSimdjsonResult(field.unescaped_key(), "Failed to get JSON
object key"));
+
+ if (field_key == key) {
+ auto value = field.value();
+
+ if constexpr (std::is_same_v<T, simdjson::ondemand::value>) {
+ return value;
+ } else {
+ return GetJsonAs<T>(value);
+ }
+ }
+ }
+
+ 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;
+
+ 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;
+}
+
+inline Status ValidateJsonObject(simdjson::ondemand::object object);
+
+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);
+}
+
} // namespace internal
} // namespace arrow
diff --git a/cpp/src/parquet/CMakeLists.txt b/cpp/src/parquet/CMakeLists.txt
index e5860d8919..606dcdc0a9 100644
--- a/cpp/src/parquet/CMakeLists.txt
+++ b/cpp/src/parquet/CMakeLists.txt
@@ -263,9 +263,9 @@ endif()
list(APPEND PARQUET_SHARED_LINK_LIBS arrow_shared)
-# Add RapidJSON & simdjson libraries
-list(APPEND PARQUET_SHARED_PRIVATE_LINK_LIBS RapidJSON simdjson::simdjson)
-list(APPEND PARQUET_STATIC_LINK_LIBS RapidJSON simdjson::simdjson)
+# Add simdjson libraries
+list(APPEND PARQUET_SHARED_PRIVATE_LINK_LIBS simdjson::simdjson)
+list(APPEND PARQUET_STATIC_LINK_LIBS simdjson::simdjson)
# These are libraries that we will link privately with parquet_shared (as they
# do not need to be linked transitively by other linkers)
@@ -323,7 +323,7 @@ if(ARROW_TESTING)
# "link" our dependencies so that include paths are configured
# correctly
target_link_libraries(parquet_testing PUBLIC ${ARROW_GTEST_GMOCK})
- list(APPEND PARQUET_TEST_LINK_LIBS parquet_testing RapidJSON)
+ list(APPEND PARQUET_TEST_LINK_LIBS parquet_testing simdjson::simdjson)
endif()
if(NOT ARROW_BUILD_SHARED)
@@ -379,6 +379,7 @@ add_parquet_test(internals-test
bloom_filter_test.cc
geospatial/statistics_test.cc
geospatial/util_internal_test.cc
+ geospatial/util_json_internal_test.cc
metadata_test.cc
page_index_test.cc
properties_test.cc
diff --git a/cpp/src/parquet/geospatial/util_json_internal.cc
b/cpp/src/parquet/geospatial/util_json_internal.cc
index 6278ab8873..44efcfc8d7 100644
--- a/cpp/src/parquet/geospatial/util_json_internal.cc
+++ b/cpp/src/parquet/geospatial/util_json_internal.cc
@@ -20,13 +20,11 @@
#include <string>
#include "arrow/extension_type.h"
-#include "arrow/json/rapidjson_defs.h" // IWYU pragma: keep
+#include "arrow/json/json_writer_internal.h"
#include "arrow/result.h"
+#include "arrow/util/simdjson_internal.h"
#include "arrow/util/string.h"
-#include <rapidjson/document.h>
-#include <rapidjson/writer.h>
-
#include "parquet/exception.h"
#include "parquet/types.h"
@@ -34,35 +32,94 @@ namespace parquet {
namespace {
::arrow::Result<std::string> GeospatialGeoArrowCrsToParquetCrs(
- const ::arrow::rapidjson::Document& document) {
- namespace rj = ::arrow::rapidjson;
+ simdjson::ondemand::object object) {
+ auto json_crs_result =
+ ::arrow::internal::GetJsonField<simdjson::ondemand::value>(object,
"crs");
- if (!document.HasMember("crs") || document["crs"].IsNull()) {
- // Parquet GEOMETRY/GEOGRAPHY do not have a concept of a null/missing
- // CRS, but an omitted one is more likely to have meant "lon/lat" than
- // a truly unspecified one (i.e., Engineering CRS with arbitrary XY units)
- return "";
+ if (!json_crs_result.ok()) {
+ if (json_crs_result.status().IsKeyError()) {
+ // Parquet GEOMETRY/GEOGRAPHY do not have a concept of a null/missing
+ // CRS, but an omitted one is more likely to have meant "lon/lat" than
+ // a truly unspecified one (i.e., Engineering CRS with arbitrary XY
units)
+ return "";
+ }
+
+ return json_crs_result.status();
}
- const auto& json_crs = document["crs"];
- if (json_crs.IsString() && (json_crs == "EPSG:4326" || json_crs ==
"OGC:CRS84")) {
- // crs can be left empty because these cases both correspond to
- // longitude/latitude in WGS84 according to the Parquet specification
+ auto json_crs = *std::move(json_crs_result);
+
+ ARROW_ASSIGN_OR_RAISE(bool is_null, ::arrow::internal::IsJsonNull(json_crs));
+ if (is_null) {
return "";
- } else if (json_crs.IsObject()) {
- // Attempt to detect common PROJJSON representations of longitude/latitude
and return
- // an empty crs to maximize compatibility with readers that do not
implement CRS
- // support. PROJJSON stores this in the "id" member like:
- // {..., "id": {"authority": "...", "code": "..."}}
- if (json_crs.HasMember("id")) {
- const auto& identifier = json_crs["id"];
- if (identifier.HasMember("authority") && identifier.HasMember("code")) {
- if (identifier["authority"] == "OGC" && identifier["code"] == "CRS84")
{
- return "";
- } else if (identifier["authority"] == "EPSG" && identifier["code"] ==
"4326") {
+ }
+
+ auto crs_string_result =
::arrow::internal::GetJsonAs<std::string_view>(json_crs);
+
+ if (crs_string_result.ok()) {
+ auto crs_string = *crs_string_result;
+
+ if (crs_string == "EPSG:4326" || crs_string == "OGC:CRS84") {
+ // crs can be left empty because these cases both correspond to
+ // longitude/latitude in WGS84 according to the Parquet specification
+ return "";
+ }
+
+ // If we could not detect a longitude/latitude CRS, just write the string
to the
+ // LogicalType crs (being sure to unescape a JSON string into a regular
string)
+ return std::string(crs_string);
+ }
+
+ ARROW_ASSIGN_OR_RAISE(
+ auto crs_object,
+ ::arrow::internal::GetJsonAs<simdjson::ondemand::object>(json_crs));
+
+ // Attempt to detect common PROJJSON representations of longitude/latitude
and return
+ // an empty crs to maximize compatibility with readers that do not implement
CRS
+ // support. PROJJSON stores this in the "id" member like:
+ // {..., "id": {"authority": "...", "code": "..."}}
+ auto identifier_result =
+ ::arrow::internal::GetJsonField<simdjson::ondemand::object>(crs_object,
"id");
+
+ if (identifier_result.ok()) {
+ auto identifier = *std::move(identifier_result);
+
+ std::optional<std::string_view> authority_string;
+ std::optional<simdjson::ondemand::value> code;
+
+ for (auto field_result : identifier) {
+ ARROW_ASSIGN_OR_RAISE(auto field,
+ ::arrow::internal::ResolveSimdjsonResult(
+ field_result, "Failed to iterate JSON
object"));
+
+ ARROW_ASSIGN_OR_RAISE(auto key,
+ ::arrow::internal::ResolveSimdjsonResult(
+ field.unescaped_key(), "Failed to get JSON
object key"));
+
+ if (key == "authority") {
+ ARROW_ASSIGN_OR_RAISE(
+ auto authority,
+ ::arrow::internal::GetJsonAs<std::string_view>(field.value()));
+ authority_string = authority;
+ } else if (key == "code") {
+ code = field.value();
+ }
+ }
+
+ if (authority_string && code) {
+ auto code_string_result =
::arrow::internal::GetJsonAs<std::string_view>(*code);
+
+ if (code_string_result.ok()) {
+ auto code_string = *code_string_result;
+
+ if ((*authority_string == "OGC" && code_string == "CRS84") ||
+ (*authority_string == "EPSG" && code_string == "4326")) {
return "";
- } else if (identifier["authority"] == "EPSG" &&
identifier["code"].IsInt() &&
- identifier["code"].GetInt() == 4326) {
+ }
+ } else if (*authority_string == "EPSG") {
+ auto code_int_result = ::arrow::internal::GetJsonAs<int64_t>(*code);
+
+ if (code_int_result.ok() && *code_int_result == 4326) {
return "";
}
}
@@ -71,20 +128,21 @@ namespace {
// If we could not detect a longitude/latitude CRS, just write the string to
the
// LogicalType crs (being sure to unescape a JSON string into a regular
string)
- if (json_crs.IsString()) {
- return json_crs.GetString();
- } else {
- rj::StringBuffer buffer;
- rj::Writer<rj::StringBuffer> writer(buffer);
- json_crs.Accept(writer);
- return buffer.GetString();
- }
+ RETURN_NOT_OK(::arrow::internal::ResolveSimdjsonResult(crs_object.reset(),
+ "Failed to reset
'crs' object")
+ .status());
+
+ ARROW_ASSIGN_OR_RAISE(auto raw_crs,
+ ::arrow::internal::ResolveSimdjsonResult(
+ crs_object.raw_json(), "Failed to get raw 'crs'
JSON"));
+
+ return ::arrow::internal::MinifyJson(raw_crs);
}
// Utility for ensuring that a Parquet CRS is valid JSON when written to
// GeoArrow metadata (without escaping it if it is already valid JSON such as
// a PROJJSON string)
-std::string EscapeCrsAsJsonIfRequired(std::string_view crs);
+::arrow::Result<std::string> EscapeCrsAsJsonIfRequired(std::string_view crs);
::arrow::Result<std::string> MakeGeoArrowCrsMetadata(
std::string_view crs,
@@ -113,64 +171,89 @@ std::string EscapeCrsAsJsonIfRequired(std::string_view
crs);
ARROW_ASSIGN_OR_RAISE(std::string projjson_value,
metadata->Get(metadata_field));
// This value should be valid JSON, but if it is not, we escape it as a
string such
// that it can be inspected by the consumer of GeoArrow.
- return R"("crs": )" + EscapeCrsAsJsonIfRequired(projjson_value) +
- R"(, "crs_type": "projjson")";
+ ARROW_ASSIGN_OR_RAISE(auto escaped,
EscapeCrsAsJsonIfRequired(projjson_value));
+ return R"("crs": )" + escaped + R"(, "crs_type": "projjson")";
}
}
// Pass on the string directly to GeoArrow. If the string is already valid
JSON,
// insert it directly into GeoArrow's "crs" field. Otherwise, escape it and
pass it as a
// string value.
- return R"("crs": )" + EscapeCrsAsJsonIfRequired(crs);
+ ARROW_ASSIGN_OR_RAISE(auto escaped, EscapeCrsAsJsonIfRequired(crs));
+
+ return R"("crs": )" + escaped;
}
-std::string EscapeCrsAsJsonIfRequired(std::string_view crs) {
- namespace rj = ::arrow::rapidjson;
- rj::Document document;
- if (document.Parse(crs.data(), crs.length()).HasParseError()) {
- rj::StringBuffer buffer;
- rj::Writer<rj::StringBuffer> writer(buffer);
- rj::Value v;
- v.SetString(crs.data(), static_cast<int32_t>(crs.size()));
- v.Accept(writer);
- return std::string(buffer.GetString());
- } else {
- return std::string(crs);
+::arrow::Result<std::string> EscapeJsonString(std::string_view value) {
+ ::arrow::json::JsonWriter writer;
+ writer.String(value);
+
+ ARROW_ASSIGN_OR_RAISE(auto escaped, writer.GetString());
+ return ::arrow::internal::MinifyJson(escaped);
+}
+
+::arrow::Result<std::string> EscapeCrsAsJsonIfRequired(std::string_view crs) {
+ simdjson::ondemand::parser parser;
+ simdjson::padded_string json(crs);
+
+ if (!::arrow::internal::ValidateJsonDocument(parser, json).ok()) {
+ return EscapeJsonString(crs);
}
+
+ return std::string(crs);
}
} // namespace
-::arrow::Result<std::shared_ptr<const LogicalType>>
LogicalTypeFromGeoArrowMetadata(
- std::string_view serialized_data) {
+::arrow::Result<std::shared_ptr<const parquet::LogicalType>>
+LogicalTypeFromGeoArrowMetadata(std::string_view serialized_data) {
// Parquet has no way to interpret a null or missing CRS, so we choose the
most likely
// intent here (that the user meant to use the default Parquet CRS)
if (serialized_data.empty() || serialized_data == "{}") {
return LogicalType::Geometry();
}
- namespace rj = ::arrow::rapidjson;
- rj::Document document;
- if (document.Parse(serialized_data.data(),
serialized_data.length()).HasParseError()) {
- return ::arrow::Status::Invalid("Invalid serialized JSON data: ",
serialized_data);
+ simdjson::ondemand::parser parser;
+ simdjson::padded_string json(serialized_data);
+
+ RETURN_NOT_OK(::arrow::internal::ValidateJsonDocument(parser, json));
+
+ // Reparse because validation consumes the On-Demand document.
+ ARROW_ASSIGN_OR_RAISE(auto document,
::arrow::internal::ResolveSimdjsonResult(
+ parser.iterate(json), "Failed to
parse JSON"));
+
+ ARROW_ASSIGN_OR_RAISE(
+ auto object,
::arrow::internal::ResolveSimdjsonResult(document.get_object(),
+ "Failed to get
JSON object"));
+
+ ARROW_ASSIGN_OR_RAISE(std::string crs,
GeospatialGeoArrowCrsToParquetCrs(object));
+
+ auto edges_field = object["edges"];
+
+ if (edges_field.error() == simdjson::NO_SUCH_FIELD) {
+ return LogicalType::Geometry(crs);
}
- ARROW_ASSIGN_OR_RAISE(std::string crs,
GeospatialGeoArrowCrsToParquetCrs(document));
+ ARROW_ASSIGN_OR_RAISE(auto edges, ::arrow::internal::ResolveSimdjsonResult(
+ edges_field, "Failed to get 'edges'
field"));
- if (document.HasMember("edges") && document["edges"] == "planar") {
+ ARROW_ASSIGN_OR_RAISE(auto edges_string,
+ ::arrow::internal::GetJsonAs<std::string_view>(edges));
+
+ if (edges_string == "planar") {
return LogicalType::Geometry(crs);
- } else if (document.HasMember("edges") && document["edges"] == "spherical") {
+ }
+
+ if (edges_string == "spherical") {
return LogicalType::Geography(crs,
LogicalType::EdgeInterpolationAlgorithm::SPHERICAL);
- } else if (document.HasMember("edges")) {
- return ::arrow::Status::Invalid("Unsupported GeoArrow edge type: ",
serialized_data);
}
- return LogicalType::Geometry(crs);
+ return ::arrow::Status::Invalid("Unsupported GeoArrow edge type: ",
serialized_data);
}
::arrow::Result<std::shared_ptr<::arrow::DataType>>
GeoArrowTypeFromLogicalType(
- const LogicalType& logical_type,
+ const parquet::LogicalType& logical_type,
const std::shared_ptr<const ::arrow::KeyValueMetadata>& metadata,
const std::shared_ptr<::arrow::DataType>& storage_type) {
// Check if we have a registered GeoArrow type to read into
diff --git a/cpp/src/parquet/geospatial/util_json_internal.h
b/cpp/src/parquet/geospatial/util_json_internal.h
index 1d43b320f8..5c18c55366 100644
--- a/cpp/src/parquet/geospatial/util_json_internal.h
+++ b/cpp/src/parquet/geospatial/util_json_internal.h
@@ -21,6 +21,7 @@
#include "arrow/util/key_value_metadata.h"
+#include "parquet/platform.h"
#include "parquet/types.h"
namespace parquet {
@@ -29,8 +30,8 @@ namespace parquet {
/// GeoArrow `ARROW:extension:metadata` (JSON-encoded extension type metadata)
///
/// Returns the appropriate LogicalType or Invalid if the metadata was invalid.
-::arrow::Result<std::shared_ptr<const LogicalType>>
LogicalTypeFromGeoArrowMetadata(
- std::string_view serialized_data);
+PARQUET_EXPORT ::arrow::Result<std::shared_ptr<const LogicalType>>
+LogicalTypeFromGeoArrowMetadata(std::string_view serialized_data);
/// \brief Compute a suitable DataType into which a GEOMETRY or GEOGRAPHY type
should be
/// read
@@ -38,7 +39,8 @@ namespace parquet {
/// The result of this function depends on whether or not "geoarrow.wkb" has
been
/// registered: if it has, the result will be the registered ExtensionType; if
it has not,
/// the result will be the given storage_type.
-::arrow::Result<std::shared_ptr<::arrow::DataType>>
GeoArrowTypeFromLogicalType(
+PARQUET_EXPORT ::arrow::Result<std::shared_ptr<::arrow::DataType>>
+GeoArrowTypeFromLogicalType(
const LogicalType& logical_type,
const std::shared_ptr<const ::arrow::KeyValueMetadata>& metadata,
const std::shared_ptr<::arrow::DataType>& storage_type);
diff --git a/cpp/src/parquet/geospatial/util_json_internal_test.cc
b/cpp/src/parquet/geospatial/util_json_internal_test.cc
new file mode 100644
index 0000000000..ae86a0cdfb
--- /dev/null
+++ b/cpp/src/parquet/geospatial/util_json_internal_test.cc
@@ -0,0 +1,68 @@
+// 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 "parquet/geospatial/util_json_internal.h"
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "arrow/testing/extension_type.h"
+#include "arrow/testing/gtest_util.h"
+#include "arrow/type.h"
+#include "arrow/util/simdjson_internal.h"
+
+#include "parquet/test_util.h"
+
+namespace parquet {
+
+TEST(UtilJsonInternal, InvalidProjJsonIsEscaped) {
+ ::arrow::ExtensionTypeGuard guard(test::geoarrow_wkb());
+
+ auto metadata = ::arrow::key_value_metadata(
+ {"proj"}, {R"({"a":[1,2,]})"}); // Invalid JSON (trailing comma)
+
+ auto logical_type = LogicalType::Geometry("projjson:proj");
+
+ ASSERT_OK_AND_ASSIGN(
+ auto type, GeoArrowTypeFromLogicalType(*logical_type, metadata,
::arrow::binary()));
+
+ auto extension = std::dynamic_pointer_cast<::arrow::ExtensionType>(type);
+ ASSERT_NE(extension, nullptr);
+
+ ASSERT_OK_AND_ASSIGN(auto actual,
+ ::arrow::internal::MinifyJson(extension->Serialize()));
+
+ EXPECT_EQ(actual,
"{\"crs\":\"{\\\"a\\\":[1,2,]}\",\"crs_type\":\"projjson\"}");
+}
+
+TEST(UtilJsonInternal, EscapedCrsKeyIsRecognized) {
+ std::string metadata =
R"({"cr\u0073":"EPSG:3857","crs_type":"authority_code"})";
+
+ ASSERT_OK_AND_ASSIGN(auto logical_type,
LogicalTypeFromGeoArrowMetadata(metadata));
+
+ ASSERT_EQ(logical_type->ToString(), "Geometry(crs=EPSG:3857)");
+}
+
+TEST(UtilJsonInternal, InvalidTrailingMetadataIsRejected) {
+ auto result = LogicalTypeFromGeoArrowMetadata(
+ R"({"crs":"EPSG:3857","edges":"planar","unused":[1,2,]})");
+
+ ASSERT_RAISES(Invalid, result);
+}
+
+} // namespace parquet
diff --git a/cpp/src/parquet/meson.build b/cpp/src/parquet/meson.build
index 9069ccb5fd..f2aff7dfa1 100644
--- a/cpp/src/parquet/meson.build
+++ b/cpp/src/parquet/meson.build
@@ -88,7 +88,7 @@ if not thrift_dep.found()
thrift_dep = thrift_proj.dependency('thrift')
endif
-parquet_deps = [arrow_dep, rapidjson_dep, thrift_dep]
+parquet_deps = [arrow_dep, simdjson_dep, thrift_dep]
if needs_parquet_encryption or get_option('parquet_require_encryption').auto()
openssl_dep = dependency('openssl', required: needs_parquet_encryption)
@@ -198,6 +198,7 @@ parquet_tests = {
'encoding_test.cc',
'geospatial/statistics_test.cc',
'geospatial/util_internal_test.cc',
+ 'geospatial/util_json_internal_test.cc',
'metadata_test.cc',
'page_index_test.cc',
'properties_test.cc',
@@ -291,6 +292,7 @@ parquet_test_dep = [
parquet_dep,
parquet_test_support_dep,
arrow_test_dep,
+ simdjson_dep,
thrift_dep,
]
diff --git a/cpp/src/parquet/reader_test.cc b/cpp/src/parquet/reader_test.cc
index 7ae9021e35..cdeee116fb 100644
--- a/cpp/src/parquet/reader_test.cc
+++ b/cpp/src/parquet/reader_test.cc
@@ -27,12 +27,6 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include "arrow/json/rapidjson_defs.h" // IWYU pragma: keep
-
-#include <rapidjson/document.h>
-#include <rapidjson/error/en.h>
-#include <rapidjson/stringbuffer.h>
-
#include "arrow/array.h"
#include "arrow/array/array_binary.h"
#include "arrow/array/builder_binary.h"
@@ -44,6 +38,7 @@
#include "arrow/util/checked_cast.h"
#include "arrow/util/config.h"
#include "arrow/util/range.h"
+#include "arrow/util/simdjson_internal.h"
#include "parquet/column_reader.h"
#include "parquet/column_scanner.h"
@@ -59,8 +54,6 @@
#include "parquet/test_util.h"
#include "parquet/types.h"
-namespace rj = arrow::rapidjson;
-
using arrow::internal::checked_pointer_cast;
using arrow::internal::Zip;
@@ -1230,14 +1223,11 @@ TEST_F(TestJSONWithLocalFile, JSONOutputSortColumns) {
namespace {
::arrow::Status CheckJsonValid(std::string_view json_string) {
- rj::Document json_doc;
- constexpr auto kParseFlags = rj::kParseFullPrecisionFlag |
rj::kParseNanAndInfFlag;
- json_doc.Parse<kParseFlags>(json_string.data(), json_string.length());
- if (json_doc.HasParseError()) {
- return ::arrow::Status::Invalid("JSON parse error at offset ",
- json_doc.GetErrorOffset(), ": ",
-
rj::GetParseError_En(json_doc.GetParseError()));
- }
+ simdjson::ondemand::parser parser;
+ auto padded_json = simdjson::padded_string(json_string);
+
+ RETURN_NOT_OK(::arrow::internal::ValidateJsonDocument(parser, padded_json));
+
return ::arrow::Status::OK();
}
diff --git a/cpp/src/parquet/schema_test.cc b/cpp/src/parquet/schema_test.cc
index 859f14a34d..704b2da79c 100644
--- a/cpp/src/parquet/schema_test.cc
+++ b/cpp/src/parquet/schema_test.cc
@@ -1581,9 +1581,9 @@ TEST(TestLogicalTypeOperation, LogicalTypeRepresentation)
{
{LogicalType::Geometry(R"(crs with "quotes" and \backslashes\)"),
R"(Geometry(crs=crs with "quotes" and \backslashes\))",
R"({"Type": "Geometry", "crs": "crs with \"quotes\" and
\\backslashes\\"})"},
- {LogicalType::Geometry("crs with control characters \u0001 and \u001F"),
- "Geometry(crs=crs with control characters \u0001 and \u001F)",
- R"({"Type": "Geometry", "crs": "crs with control characters \u0001 and
\u001F"})"},
+ {LogicalType::Geometry("crs with control characters \u0001 and \u001f"),
+ "Geometry(crs=crs with control characters \u0001 and \u001f)",
+ R"({"Type": "Geometry", "crs": "crs with control characters \u0001 and
\u001f"})"},
{LogicalType::Geography(), "Geography(crs=, algorithm=spherical)",
R"({"Type": "Geography"})"},
{LogicalType::Geography("srid:1234",
diff --git a/cpp/src/parquet/types.cc b/cpp/src/parquet/types.cc
index 9d7604faec..cc3199f367 100644
--- a/cpp/src/parquet/types.cc
+++ b/cpp/src/parquet/types.cc
@@ -24,16 +24,13 @@
#include <sstream>
#include <string>
-#include "arrow/json/rapidjson_defs.h" // IWYU pragma: keep
+#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 <rapidjson/document.h>
-#include <rapidjson/writer.h>
-
#include "parquet/exception.h"
#include "parquet/thrift_internal.h"
#include "parquet/types.h"
@@ -1785,13 +1782,9 @@ namespace {
void WriteCrsKeyAndValue(const std::string_view crs, std::ostream& json) {
// There is no restriction on the crs value here, and it may contain quotes
// or backslashes that would result in invalid JSON if unescaped.
- namespace rj = ::arrow::rapidjson;
- rj::StringBuffer buffer;
- rj::Writer<rj::StringBuffer> writer(buffer);
- rj::Value v;
- v.SetString(crs.data(), static_cast<int32_t>(crs.size()));
- v.Accept(writer);
- json << R"(, "crs": )" << buffer.GetString();
+ ::arrow::json::JsonWriter writer;
+ writer.String(crs);
+ json << R"(, "crs": )" << writer.GetString().ValueUnsafe();
}
} // namespace
diff --git a/cpp/subprojects/simdjson.wrap b/cpp/subprojects/simdjson.wrap
new file mode 100644
index 0000000000..31d9de3c27
--- /dev/null
+++ b/cpp/subprojects/simdjson.wrap
@@ -0,0 +1,23 @@
+# 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.
+
+[wrap-file]
+source_url =
https://github.com/simdjson/simdjson/archive/refs/tags/v4.6.4.tar.gz
+source_filename = simdjson-v4.6.4.tar.gz
+source_hash = b091107844fe928158c5c2265c20360fff312889ddf7ebc4528a0f0f8f2ff9cd
+directory = simdjson-v4.6.4
+method = cmake