Skip to content

Commit f7a9960

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

File tree

4 files changed

+120
-0
lines changed

4 files changed

+120
-0
lines changed

src/iceberg/expression/json_serde.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@
2727

2828
#include "iceberg/expression/json_serde_internal.h"
2929
#include "iceberg/expression/literal.h"
30+
#include "iceberg/expression/term.h"
31+
#include "iceberg/transform.h"
3032
#include "iceberg/util/checked_cast.h"
3133
#include "iceberg/util/json_util_internal.h"
3234
#include "iceberg/util/macros.h"
3335

3436
namespace iceberg {
3537
namespace {
38+
// JSON field names
39+
constexpr std::string_view kType = "type";
40+
constexpr std::string_view kTerm = "term";
41+
constexpr std::string_view kTransform = "transform";
3642
// Expression type strings
3743
constexpr std::string_view kTypeTrue = "true";
3844
constexpr std::string_view kTypeFalse = "false";
@@ -123,6 +129,41 @@ nlohmann::json ToJson(Expression::Operation op) {
123129
return json;
124130
}
125131

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