Skip to content

Commit 602bef1

Browse files
author
Innocent
committed
feat: add json serde for expressions
1 parent 43b83c5 commit 602bef1

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

src/iceberg/expression/json_serde.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* under the License.
1818
*/
1919

20+
#include <cctype>
2021
#include <format>
2122
#include <ranges>
2223
#include <string>
@@ -27,12 +28,18 @@
2728

2829
#include "iceberg/expression/json_serde_internal.h"
2930
#include "iceberg/expression/literal.h"
31+
#include "iceberg/expression/term.h"
32+
#include "iceberg/transform.h"
3033
#include "iceberg/util/checked_cast.h"
3134
#include "iceberg/util/json_util_internal.h"
3235
#include "iceberg/util/macros.h"
3336

3437
namespace iceberg {
3538
namespace {
39+
// JSON field names
40+
constexpr std::string_view kType = "type";
41+
constexpr std::string_view kTerm = "term";
42+
constexpr std::string_view kTransform = "transform";
3643
// Expression type strings
3744
constexpr std::string_view kTypeTrue = "true";
3845
constexpr std::string_view kTypeFalse = "false";
@@ -123,6 +130,41 @@ nlohmann::json ToJson(Expression::Operation op) {
123130
return json;
124131
}
125132

133+
nlohmann::json ToJson(const NamedReference& ref) { return std::string(ref.name()); }
134+
135+
Result<std::shared_ptr<NamedReference>> NamedReferenceFromJson(
136+
const nlohmann::json& json) {
137+
if (!json.is_string()) {
138+
return JsonParseError("Expected string for named reference");
139+
}
140+
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(json.get<std::string>()));
141+
return std::shared_ptr<NamedReference>(std::move(ref));
142+
}
143+
144+
nlohmann::json ToJson(const UnboundTransform& transform) {
145+
auto& mutable_transform = const_cast<UnboundTransform&>(transform);
146+
nlohmann::json json;
147+
json[kType] = kTransform;
148+
json[kTransform] = transform.transform()->ToString();
149+
json[kTerm] = std::string(mutable_transform.reference()->name());
150+
return json;
151+
}
152+
153+
Result<std::shared_ptr<UnboundTransform>> UnboundTransformFromJson(
154+
const nlohmann::json& json) {
155+
if (json.is_object() && json.contains(kType) && json[kType] == kTransform &&
156+
json.contains(kTerm)) {
157+
ICEBERG_ASSIGN_OR_RAISE(auto transform_str,
158+
GetJsonValue<std::string>(json, kTransform));
159+
ICEBERG_ASSIGN_OR_RAISE(auto transform, TransformFromString(transform_str));
160+
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReferenceFromJson(json[kTerm]));
161+
ICEBERG_ASSIGN_OR_RAISE(auto result,
162+
UnboundTransform::Make(std::move(ref), std::move(transform)));
163+
return std::shared_ptr<UnboundTransform>(std::move(result));
164+
}
165+
return JsonParseError("Invalid unbound transform json: {}", SafeDumpJson(json));
166+
}
167+
126168
Result<std::shared_ptr<Expression>> ExpressionFromJson(const nlohmann::json& json) {
127169
// Handle boolean
128170
if (json.is_boolean()) {

src/iceberg/expression/json_serde_internal.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@ ICEBERG_EXPORT Result<std::shared_ptr<Expression>> ExpressionFromJson(
5757
/// \return A JSON object representing the expression
5858
ICEBERG_EXPORT nlohmann::json ToJson(const Expression& expr);
5959

60+
/// \brief Deserializes a JSON object into a NamedReference.
61+
///
62+
/// \param json A JSON object representing a named reference
63+
/// \return A shared pointer to the deserialized NamedReference or an error
64+
ICEBERG_EXPORT Result<std::shared_ptr<NamedReference>> NamedReferenceFromJson(
65+
const nlohmann::json& json);
66+
67+
/// \brief Serializes a NamedReference into its JSON representation.
68+
///
69+
/// \param ref The named reference to serialize
70+
/// \return A JSON object representing the named reference
71+
ICEBERG_EXPORT nlohmann::json ToJson(const NamedReference& ref);
72+
73+
/// \brief Serializes an UnboundTransform into its JSON representation.
74+
///
75+
/// \param transform The unbound transform to serialize
76+
/// \return A JSON object representing the unbound transform
77+
ICEBERG_EXPORT nlohmann::json ToJson(const UnboundTransform& transform);
78+
79+
/// \brief Deserializes a JSON object into an UnboundTransform.
80+
///
81+
/// \param json A JSON object representing an unbound transform
82+
/// \return A shared pointer to the deserialized UnboundTransform or an error
83+
ICEBERG_EXPORT Result<std::shared_ptr<UnboundTransform>> UnboundTransformFromJson(
84+
const nlohmann::json& json);
85+
6086
/// Check if an operation is a unary predicate
6187
ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op);
6288

src/iceberg/test/expression_json_test.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "iceberg/expression/predicate.h"
3232
#include "iceberg/expression/term.h"
3333
#include "iceberg/test/matchers.h"
34+
#include "iceberg/transform.h"
3435

3536
namespace iceberg {
3637

@@ -63,4 +64,53 @@ TEST(ExpressionJsonTest, OperationTypeTests) {
6364
EXPECT_FALSE(IsUnaryOperation(Expression::Operation::kTrue));
6465
}
6566

67+
TEST(ExpressionJsonTest, NameReferenceRoundTrip) {
68+
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("col_name"));
69+
auto json = ToJson(*ref);
70+
EXPECT_EQ(json.get<std::string>(), "col_name");
71+
72+
ICEBERG_UNWRAP_OR_FAIL(auto parsed, NamedReferenceFromJson(json));
73+
EXPECT_EQ(parsed->name(), "col_name");
74+
}
75+
76+
TEST(ExpressionJsonTest, UnboundTransfromRoundTrip) {
77+
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("ts"));
78+
auto transform = Transform::Day();
79+
ICEBERG_UNWRAP_OR_FAIL(auto unbound, UnboundTransform::Make(std::move(ref), transform));
80+
81+
auto json = ToJson(*unbound);
82+
EXPECT_EQ(json["type"], "transform");
83+
EXPECT_EQ(json["transform"], "day");
84+
EXPECT_EQ(json["term"], "ts");
85+
86+
ICEBERG_UNWRAP_OR_FAIL(auto parsed, UnboundTransformFromJson(json));
87+
EXPECT_EQ(parsed->reference()->name(), unbound->reference()->name());
88+
EXPECT_EQ(parsed->transform()->transform_type(),
89+
unbound->transform()->transform_type());
90+
EXPECT_EQ(parsed->transform()->ToString(), unbound->transform()->ToString());
91+
}
92+
93+
TEST(ExpressionJsonTest, BucketTransform) {
94+
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("id"));
95+
ICEBERG_UNWRAP_OR_FAIL(auto unbound,
96+
UnboundTransform::Make(std::move(ref), Transform::Bucket(16)));
97+
98+
auto json = ToJson(*unbound);
99+
EXPECT_EQ(json["type"], "transform");
100+
EXPECT_EQ(json["transform"], "bucket[16]");
101+
EXPECT_EQ(json["term"], "id");
102+
103+
ICEBERG_UNWRAP_OR_FAIL(auto parsed, UnboundTransformFromJson(json));
104+
EXPECT_EQ(parsed->transform()->transform_type(),
105+
unbound->transform()->transform_type());
106+
EXPECT_EQ(parsed->transform()->ToString(), unbound->transform()->ToString());
107+
}
108+
109+
TEST(ExpressionJsonTest, InvalidInput) {
110+
EXPECT_THAT(UnboundTransformFromJson(nlohmann::json::object()),
111+
IsError(ErrorKind::kJsonParseError));
112+
EXPECT_THAT(UnboundTransformFromJson(nlohmann::json{{"type", "other"}}),
113+
IsError(ErrorKind::kJsonParseError));
114+
}
115+
66116
} // namespace iceberg

src/iceberg/type_fwd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class Expression;
131131
class Literal;
132132
class Term;
133133
class UnboundPredicate;
134+
class NamedReference;
135+
class UnboundTransform;
136+
class Transform;
134137

135138
/// \brief Evaluator.
136139
class Evaluator;

0 commit comments

Comments
 (0)