Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions cpp/src/arrow/json/from_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,14 @@ class BooleanConverter final : public ConcreteConverter<BooleanConverter> {
if (is_null) {
return AppendNull();
}
int64_t int_value;
if (json_obj.get(int_value) == simdjson::SUCCESS) {
ARROW_ASSIGN_OR_RAISE(auto json_type, internal::GetJsonType(json_obj));
if (json_type == sj::json_type::number) {
ARROW_ASSIGN_OR_RAISE(auto int_value, internal::GetJsonAs<int64_t>(json_obj));
return builder_->Append(int_value != 0);
}
if (json_type != sj::json_type::boolean) {
return Status::Invalid("Expected JSON boolean for type ", type_->ToString());
}
ARROW_ASSIGN_OR_RAISE(bool bool_value, internal::GetJsonAs<bool>(json_obj));
return builder_->Append(bool_value);
}
Expand Down Expand Up @@ -424,14 +428,18 @@ class TimestampConverter final : public ConcreteConverter<TimestampConverter> {
if (is_null) {
return this->AppendNull();
}
ARROW_ASSIGN_OR_RAISE(auto json_type, internal::GetJsonType(json_obj));
int64_t value;
std::string_view view;
if (json_obj.get(view) == simdjson::SUCCESS) {
if (json_type == sj::json_type::string) {
ARROW_ASSIGN_OR_RAISE(auto view, internal::GetJsonAs<std::string_view>(json_obj));
if (!ParseValue(*timestamp_type_, view.data(), view.size(), &value)) {
return Status::Invalid("couldn't parse timestamp from ", view);
}
} else {
} else if (json_type == sj::json_type::number) {
RETURN_NOT_OK(ConvertNumber<Int64Type>(json_obj, *this->type_, &value));
} else {
return Status::Invalid("Expected JSON string or number for type ",
type_->ToString());
}
return builder_->Append(value);
}
Expand Down Expand Up @@ -749,8 +757,9 @@ class StructConverter final : public ConcreteConverter<StructConverter> {
if (is_null) {
return this->AppendNull();
}
sj::array array;
if (json_obj.get(array) == simdjson::SUCCESS) {
ARROW_ASSIGN_OR_RAISE(auto json_type, internal::GetJsonType(json_obj));
if (json_type == sj::json_type::array) {
ARROW_ASSIGN_OR_RAISE(auto array, internal::GetJsonAs<sj::array>(json_obj));
auto expected_size = static_cast<size_t>(type_->num_fields());
size_t size;
if (array.count_elements().get(size) != simdjson::SUCCESS) {
Expand All @@ -770,6 +779,10 @@ class StructConverter final : public ConcreteConverter<StructConverter> {
}
return builder_->Append();
}
if (json_type != sj::json_type::object) {
return Status::Invalid("Expected JSON array or object for type ",
type_->ToString());
}
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
Expand Down
27 changes: 27 additions & 0 deletions cpp/src/arrow/json/from_string_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ TEST(TestNullFromString, Errors) {
ASSERT_RAISES(Invalid, ArrayFromJSONString(type, "[NaN]"));
}

TEST(TestNullFromString, MalformedNull) {
for (const auto& type : {null(), boolean(), int32(), timestamp(TimeUnit::SECOND),
struct_({field("a", int32())})}) {
SCOPED_TRACE(type->ToString());
for (const auto* json : {"[nul]", "[nullx]"}) {
SCOPED_TRACE(json);
ASSERT_RAISES(Invalid, ArrayFromJSONString(type, json));
}
}
}

TEST(TestBooleanFromString, Basics) {
std::shared_ptr<DataType> type = boolean();

Expand Down Expand Up @@ -1211,6 +1222,22 @@ TEST(TestStructFromString, NestedStruct) {
AssertArraysEqual(*expected, *actual);
}

TEST(TestStructFromString, NestedStructObjectNotation) {
auto type =
struct_({field("c1", struct_({field("c11", int32()),
field("c12", struct_({field("c121", int32())}))})),
field("c2", int64())});
constexpr auto json = R"([
[{"c11": null, "c12": {"c121": -1}}, 1],
[{"c11": 2, "c12": {"c121": null}}, 2]
])";
ASSERT_OK_AND_ASSIGN(auto actual, ArrayFromJSONString(type, json));
ASSERT_OK(actual->ValidateFull());
ASSERT_OK_AND_ASSIGN(
auto expected, ArrayFromJSONString(type, "[[[null, [-1]], 1], [[2, [null]], 2]]"));
AssertArraysEqual(*expected, *actual);
}

TEST(TestStructFromString, Errors) {
auto field_a = field("a", int8());
auto field_b = field("b", boolean());
Expand Down
40 changes: 14 additions & 26 deletions cpp/src/arrow/util/simdjson_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,45 +274,33 @@ Status VisitJsonValue(simdjson::ondemand::value value, ObjectFn&& object_fn,

ARROW_EXPORT const char* JsonTypeName(simdjson::ondemand::json_type type);

// Inspect the type without consuming the value or validating its contents.
inline Result<simdjson::ondemand::json_type> GetJsonType(
simdjson::ondemand::value& value) {
return ResolveSimdjsonResult(value.type(), "Failed to determine JSON type");
}

// Result<bool> because peeking the nonRootScalar can fail (parsed lazily)
ARROW_EXPORT Result<bool> IsJsonNull(simdjson::ondemand::value& value);

template <typename SimdjsonValueType>
Result<SimdjsonValueType> GetJsonAs(simdjson::ondemand::value& value) {
SimdjsonValueType typed_value{};
simdjson::error_code error_code;
ARROW_ASSIGN_OR_RAISE(auto json_type, GetJsonType(value));

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;
ARROW_ASSIGN_OR_RAISE(bool is_null, IsJsonNull(value));
if (is_null) {
return SimdjsonNull{};
}
return Status::Invalid("Expected null, got JSON type ", JsonTypeName(json_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 {
SimdjsonValueType typed_value{};
if (value.get(typed_value) != simdjson::SUCCESS) {
return Status::Invalid("Expected ", JsonTypeName<SimdjsonValueType>(),
", got JSON type ", JsonTypeName(json_type));
}
return typed_value;
}

return typed_value;
}

template <typename T>
Expand Down
Loading