Skip to content

Commit 4a5fe91

Browse files
authored
feat: sort field/order json serialize/deserialization (#64)
1 parent e47f972 commit 4a5fe91

File tree

10 files changed

+342
-29
lines changed

10 files changed

+342
-29
lines changed

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(ICEBERG_INCLUDES "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src>"
2020
set(ICEBERG_SOURCES
2121
arrow_c_data_internal.cc
2222
demo.cc
23+
json_internal.cc
2324
schema.cc
2425
schema_field.cc
2526
schema_internal.cc

src/iceberg/json_internal.cc

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/json_internal.h"
21+
22+
#include <format>
23+
24+
#include <nlohmann/json.hpp>
25+
26+
#include "iceberg/sort_order.h"
27+
#include "iceberg/transform.h"
28+
#include "iceberg/util/formatter.h"
29+
30+
namespace iceberg {
31+
32+
namespace {
33+
34+
constexpr std::string_view kTransform = "transform";
35+
constexpr std::string_view kSourceId = "source-id";
36+
constexpr std::string_view kDirection = "direction";
37+
constexpr std::string_view kNullOrder = "null-order";
38+
39+
constexpr std::string_view kOrderId = "order-id";
40+
constexpr std::string_view kFields = "fields";
41+
42+
// --- helper for safe JSON extraction ---
43+
template <typename T>
44+
expected<T, Error> GetJsonValue(const nlohmann::json& json, std::string_view key) {
45+
if (!json.contains(key)) {
46+
return unexpected<Error>({.kind = ErrorKind::kInvalidArgument,
47+
.message = "Missing key: " + std::string(key)});
48+
}
49+
try {
50+
return json.at(key).get<T>();
51+
} catch (const std::exception& ex) {
52+
return unexpected<Error>({.kind = ErrorKind::kInvalidArgument,
53+
.message = std::string("Failed to parse key: ") +
54+
key.data() + ", " + ex.what()});
55+
}
56+
}
57+
58+
#define TRY_ASSIGN(json_value, expr) \
59+
auto _tmp_##json_value = (expr); \
60+
if (!_tmp_##json_value) return unexpected(_tmp_##json_value.error()); \
61+
auto json_value = std::move(_tmp_##json_value.value());
62+
} // namespace
63+
64+
nlohmann::json ToJson(const SortField& sort_field) {
65+
nlohmann::json json;
66+
json[kTransform] = std::format("{}", *sort_field.transform());
67+
json[kSourceId] = sort_field.source_id();
68+
json[kDirection] = SortDirectionToString(sort_field.direction());
69+
json[kNullOrder] = NullOrderToString(sort_field.null_order());
70+
return json;
71+
}
72+
73+
nlohmann::json ToJson(const SortOrder& sort_order) {
74+
nlohmann::json json;
75+
json[kOrderId] = sort_order.order_id();
76+
77+
nlohmann::json fields_json = nlohmann::json::array();
78+
for (const auto& field : sort_order.fields()) {
79+
fields_json.push_back(ToJson(field));
80+
}
81+
json[kFields] = fields_json;
82+
return json;
83+
}
84+
85+
expected<std::unique_ptr<SortField>, Error> SortFieldFromJson(
86+
const nlohmann::json& json) {
87+
TRY_ASSIGN(transform_str, GetJsonValue<std::string>(json, kTransform));
88+
TRY_ASSIGN(transform, TransformFunctionFromString(transform_str));
89+
TRY_ASSIGN(source_id, GetJsonValue<int32_t>(json, kSourceId));
90+
TRY_ASSIGN(direction_str, GetJsonValue<std::string>(json, kDirection));
91+
TRY_ASSIGN(direction, SortDirectionFromString(direction_str));
92+
TRY_ASSIGN(null_order_str, GetJsonValue<std::string>(json, kNullOrder));
93+
TRY_ASSIGN(null_order, NullOrderFromString(null_order_str));
94+
95+
return std::make_unique<SortField>(source_id, std::move(transform), direction,
96+
null_order);
97+
}
98+
99+
expected<std::unique_ptr<SortOrder>, Error> SortOrderFromJson(
100+
const nlohmann::json& json) {
101+
TRY_ASSIGN(order_id, GetJsonValue<int32_t>(json, kOrderId));
102+
103+
std::vector<SortField> sort_fields;
104+
for (const auto& field_json : json.at(kFields)) {
105+
TRY_ASSIGN(sort_field, SortFieldFromJson(field_json));
106+
sort_fields.push_back(*sort_field);
107+
}
108+
109+
return std::make_unique<SortOrder>(order_id, std::move(sort_fields));
110+
}
111+
112+
} // namespace iceberg

src/iceberg/json_internal.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <memory>
23+
24+
#include <nlohmann/json_fwd.hpp>
25+
26+
#include "iceberg/error.h"
27+
#include "iceberg/expected.h"
28+
#include "iceberg/type_fwd.h"
29+
30+
namespace iceberg {
31+
/// \brief Serializes a `SortField` object to JSON.
32+
///
33+
/// This function converts a `SortField` object into a JSON representation.
34+
/// The resulting JSON object includes the transform type, source ID, sort direction, and
35+
/// null ordering.
36+
///
37+
/// \param sort_field The `SortField` object to be serialized.
38+
/// \return A JSON object representing the `SortField` in the form of key-value pairs.
39+
nlohmann::json ToJson(const SortField& sort_field);
40+
41+
/// \brief Serializes a `SortOrder` object to JSON.
42+
///
43+
/// This function converts a `SortOrder` object into a JSON representation.
44+
/// The resulting JSON includes the order ID and a list of `SortField` objects.
45+
/// Each `SortField` is serialized as described in the `ToJson(SortField)` function.
46+
///
47+
/// \param sort_order The `SortOrder` object to be serialized.
48+
/// \return A JSON object representing the `SortOrder` with its order ID and fields array.
49+
nlohmann::json ToJson(const SortOrder& sort_order);
50+
51+
/// \brief Deserializes a JSON object into a `SortField` object.
52+
///
53+
/// This function parses the provided JSON and creates a `SortField` object.
54+
/// It expects the JSON object to contain keys for the transform, source ID, direction,
55+
/// and null order.
56+
///
57+
/// \param json The JSON object representing a `SortField`.
58+
/// \return An `expected` value containing either a `SortField` object or an error. If the
59+
/// JSON is malformed or missing expected fields, an error will be returned.
60+
expected<std::unique_ptr<SortField>, Error> SortFieldFromJson(const nlohmann::json& json);
61+
62+
/// \brief Deserializes a JSON object into a `SortOrder` object.
63+
///
64+
/// This function parses the provided JSON and creates a `SortOrder` object.
65+
/// It expects the JSON object to contain the order ID and a list of `SortField` objects.
66+
/// Each `SortField` will be parsed using the `SortFieldFromJson` function.
67+
///
68+
/// \param json The JSON object representing a `SortOrder`.
69+
/// \return An `expected` value containing either a `SortOrder` object or an error. If the
70+
/// JSON is malformed or missing expected fields, an error will be returned.
71+
expected<std::unique_ptr<SortOrder>, Error> SortOrderFromJson(const nlohmann::json& json);
72+
73+
} // namespace iceberg

src/iceberg/sort_field.cc

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,6 @@
2727

2828
namespace iceberg {
2929

30-
namespace {
31-
/// \brief Get the relative sort direction name
32-
constexpr std::string_view ToString(SortDirection direction) {
33-
switch (direction) {
34-
case SortDirection::kAscending:
35-
return "asc";
36-
case SortDirection::kDescending:
37-
return "desc";
38-
default:
39-
return "invalid";
40-
}
41-
}
42-
43-
/// \brief Get the relative null order name
44-
constexpr std::string_view ToString(NullOrder null_order) {
45-
switch (null_order) {
46-
case NullOrder::kFirst:
47-
return "nulls-first";
48-
case NullOrder::kLast:
49-
return "nulls-last";
50-
default:
51-
return "invalid";
52-
}
53-
}
54-
} // namespace
55-
5630
SortField::SortField(int32_t source_id, std::shared_ptr<TransformFunction> transform,
5731
SortDirection direction, NullOrder null_order)
5832
: source_id_(source_id),
@@ -73,7 +47,7 @@ NullOrder SortField::null_order() const { return null_order_; }
7347
std::string SortField::ToString() const {
7448
return std::format(
7549
"sort_field(source_id={}, transform={}, direction={}, null_order={})", source_id_,
76-
*transform_, iceberg::ToString(direction_), iceberg::ToString(null_order_));
50+
*transform_, SortDirectionToString(direction_), NullOrderToString(null_order_));
7751
}
7852

7953
bool SortField::Equals(const SortField& other) const {

src/iceberg/sort_field.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <string_view>
2929
#include <vector>
3030

31+
#include "iceberg/error.h"
32+
#include "iceberg/expected.h"
3133
#include "iceberg/iceberg_export.h"
3234
#include "iceberg/type_fwd.h"
3335
#include "iceberg/util/formattable.h"
@@ -41,13 +43,52 @@ enum class SortDirection {
4143
/// Descending
4244
kDescending,
4345
};
46+
/// \brief Get the relative sort direction name
47+
ICEBERG_EXPORT constexpr std::string_view SortDirectionToString(SortDirection direction) {
48+
switch (direction) {
49+
case SortDirection::kAscending:
50+
return "asc";
51+
case SortDirection::kDescending:
52+
return "desc";
53+
default:
54+
return "invalid";
55+
}
56+
}
57+
/// \brief Get the relative sort direction from name
58+
ICEBERG_EXPORT constexpr expected<SortDirection, Error> SortDirectionFromString(
59+
std::string_view str) {
60+
if (str == "asc") return SortDirection::kAscending;
61+
if (str == "desc") return SortDirection::kDescending;
62+
return unexpected<Error>(
63+
{.kind = ErrorKind::kInvalidArgument,
64+
.message = "Invalid SortDirection string: " + std::string(str)});
65+
}
4466

4567
enum class NullOrder {
4668
/// Nulls are sorted first
4769
kFirst,
4870
/// Nulls are sorted last
4971
kLast,
5072
};
73+
/// \brief Get the relative null order name
74+
ICEBERG_EXPORT constexpr std::string_view NullOrderToString(NullOrder null_order) {
75+
switch (null_order) {
76+
case NullOrder::kFirst:
77+
return "nulls-first";
78+
case NullOrder::kLast:
79+
return "nulls-last";
80+
default:
81+
return "invalid";
82+
}
83+
}
84+
/// \brief Get the relative null order from name
85+
ICEBERG_EXPORT constexpr expected<NullOrder, Error> NullOrderFromString(
86+
std::string_view str) {
87+
if (str == "nulls-first") return NullOrder::kFirst;
88+
if (str == "nulls-last") return NullOrder::kLast;
89+
return unexpected<Error>({.kind = ErrorKind::kInvalidArgument,
90+
.message = "Invalid NullOrder string: " + std::string(str)});
91+
}
5192

5293
/// \brief a field with its transform.
5394
class ICEBERG_EXPORT SortField : public util::Formattable {

src/iceberg/transform.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,14 @@ expected<ArrowArray, Error> IdentityTransformFunction::Transform(
7272
.message = "IdentityTransformFunction::Transform"});
7373
}
7474

75+
expected<std::unique_ptr<TransformFunction>, Error> TransformFunctionFromString(
76+
std::string_view str) {
77+
if (str == "identity") {
78+
return std::make_unique<IdentityTransformFunction>();
79+
}
80+
return unexpected<Error>(
81+
{.kind = ErrorKind::kInvalidArgument,
82+
.message = "Invalid TransformFunction string: " + std::string(str)});
83+
}
84+
7585
} // namespace iceberg

src/iceberg/transform.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ class ICEBERG_EXPORT TransformFunction : public util::Formattable {
8282
TransformType transform_type_;
8383
};
8484

85-
class IdentityTransformFunction : public TransformFunction {
85+
ICEBERG_EXPORT expected<std::unique_ptr<TransformFunction>, Error>
86+
TransformFunctionFromString(std::string_view str);
87+
88+
class ICEBERG_EXPORT IdentityTransformFunction : public TransformFunction {
8689
public:
8790
IdentityTransformFunction();
8891
/// \brief Transform will take an input array and transform it into a new array.

src/iceberg/type_fwd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class Transaction;
9696
class HistoryEntry;
9797
class PartitionSpec;
9898
class Snapshot;
99+
class SortField;
99100
class SortOrder;
100101
class StructLike;
101102
class TableMetadata;

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ fetchcontent_makeavailable(googletest)
2525

2626
add_executable(schema_test)
2727
target_sources(schema_test
28-
PRIVATE schema_test.cc
28+
PRIVATE json_internal_test.cc
29+
schema_test.cc
2930
schema_field_test.cc
3031
schema_json_test.cc
3132
type_test.cc

0 commit comments

Comments
 (0)