This is an automated email from the ASF dual-hosted git repository.

gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 43b83c53 feat: add json serde for expression operations (#532)
43b83c53 is described below

commit 43b83c53f40c17991a4595c334694908e5392632
Author: Innocent Djiofack <[email protected]>
AuthorDate: Sat Jan 31 01:03:16 2026 -0700

    feat: add json serde for expression operations (#532)
---
 src/iceberg/CMakeLists.txt                   |   1 +
 src/iceberg/expression/json_serde.cc         | 150 +++++++++++++++++++++++++++
 src/iceberg/expression/json_serde_internal.h |  66 ++++++++++++
 src/iceberg/meson.build                      |   1 +
 src/iceberg/test/CMakeLists.txt              |   1 +
 src/iceberg/test/expression_json_test.cc     |  66 ++++++++++++
 src/iceberg/test/meson.build                 |   1 +
 7 files changed, 286 insertions(+)

diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt
index 5d3ff33b..ce995cd4 100644
--- a/src/iceberg/CMakeLists.txt
+++ b/src/iceberg/CMakeLists.txt
@@ -31,6 +31,7 @@ set(ICEBERG_SOURCES
     expression/expression.cc
     expression/expressions.cc
     expression/inclusive_metrics_evaluator.cc
+    expression/json_serde.cc
     expression/literal.cc
     expression/manifest_evaluator.cc
     expression/predicate.cc
diff --git a/src/iceberg/expression/json_serde.cc 
b/src/iceberg/expression/json_serde.cc
new file mode 100644
index 00000000..0fd7dd01
--- /dev/null
+++ b/src/iceberg/expression/json_serde.cc
@@ -0,0 +1,150 @@
+/*
+ * 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 <format>
+#include <ranges>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <nlohmann/json.hpp>
+
+#include "iceberg/expression/json_serde_internal.h"
+#include "iceberg/expression/literal.h"
+#include "iceberg/util/checked_cast.h"
+#include "iceberg/util/json_util_internal.h"
+#include "iceberg/util/macros.h"
+
+namespace iceberg {
+namespace {
+// Expression type strings
+constexpr std::string_view kTypeTrue = "true";
+constexpr std::string_view kTypeFalse = "false";
+constexpr std::string_view kTypeEq = "eq";
+constexpr std::string_view kTypeAnd = "and";
+constexpr std::string_view kTypeOr = "or";
+constexpr std::string_view kTypeNot = "not";
+constexpr std::string_view kTypeIn = "in";
+constexpr std::string_view kTypeNotIn = "not-in";
+constexpr std::string_view kTypeLt = "lt";
+constexpr std::string_view kTypeLtEq = "lt-eq";
+constexpr std::string_view kTypeGt = "gt";
+constexpr std::string_view kTypeGtEq = "gt-eq";
+constexpr std::string_view kTypeNotEq = "not-eq";
+constexpr std::string_view kTypeStartsWith = "starts-with";
+constexpr std::string_view kTypeNotStartsWith = "not-starts-with";
+constexpr std::string_view kTypeIsNull = "is-null";
+constexpr std::string_view kTypeNotNull = "not-null";
+constexpr std::string_view kTypeIsNan = "is-nan";
+constexpr std::string_view kTypeNotNan = "not-nan";
+constexpr std::string_view kTypeCount = "count";
+constexpr std::string_view kTypeCountNull = "count-null";
+constexpr std::string_view kTypeCountStar = "count-star";
+constexpr std::string_view kTypeMin = "min";
+constexpr std::string_view kTypeMax = "max";
+}  // namespace
+
+bool IsUnaryOperation(Expression::Operation op) {
+  switch (op) {
+    case Expression::Operation::kIsNull:
+    case Expression::Operation::kNotNull:
+    case Expression::Operation::kIsNan:
+    case Expression::Operation::kNotNan:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool IsSetOperation(Expression::Operation op) {
+  switch (op) {
+    case Expression::Operation::kIn:
+    case Expression::Operation::kNotIn:
+      return true;
+    default:
+      return false;
+  }
+}
+
+Result<Expression::Operation> OperationTypeFromJson(const nlohmann::json& 
json) {
+  if (!json.is_string()) {
+    return JsonParseError("Unable to create operation. Json value is not a 
string");
+  }
+  auto typeStr = json.get<std::string>();
+  if (typeStr == kTypeTrue) return Expression::Operation::kTrue;
+  if (typeStr == kTypeFalse) return Expression::Operation::kFalse;
+  if (typeStr == kTypeAnd) return Expression::Operation::kAnd;
+  if (typeStr == kTypeOr) return Expression::Operation::kOr;
+  if (typeStr == kTypeNot) return Expression::Operation::kNot;
+  if (typeStr == kTypeEq) return Expression::Operation::kEq;
+  if (typeStr == kTypeNotEq) return Expression::Operation::kNotEq;
+  if (typeStr == kTypeLt) return Expression::Operation::kLt;
+  if (typeStr == kTypeLtEq) return Expression::Operation::kLtEq;
+  if (typeStr == kTypeGt) return Expression::Operation::kGt;
+  if (typeStr == kTypeGtEq) return Expression::Operation::kGtEq;
+  if (typeStr == kTypeIn) return Expression::Operation::kIn;
+  if (typeStr == kTypeNotIn) return Expression::Operation::kNotIn;
+  if (typeStr == kTypeIsNull) return Expression::Operation::kIsNull;
+  if (typeStr == kTypeNotNull) return Expression::Operation::kNotNull;
+  if (typeStr == kTypeIsNan) return Expression::Operation::kIsNan;
+  if (typeStr == kTypeNotNan) return Expression::Operation::kNotNan;
+  if (typeStr == kTypeStartsWith) return Expression::Operation::kStartsWith;
+  if (typeStr == kTypeNotStartsWith) return 
Expression::Operation::kNotStartsWith;
+  if (typeStr == kTypeCount) return Expression::Operation::kCount;
+  if (typeStr == kTypeCountNull) return Expression::Operation::kCountNull;
+  if (typeStr == kTypeCountStar) return Expression::Operation::kCountStar;
+  if (typeStr == kTypeMin) return Expression::Operation::kMin;
+  if (typeStr == kTypeMax) return Expression::Operation::kMax;
+
+  return JsonParseError("Unknown expression type: {}", typeStr);
+}
+
+nlohmann::json ToJson(Expression::Operation op) {
+  std::string json(ToString(op));
+  std::ranges::transform(json, json.begin(), [](unsigned char c) -> char {
+    return (c == '_') ? '-' : static_cast<char>(std::tolower(c));
+  });
+  return json;
+}
+
+Result<std::shared_ptr<Expression>> ExpressionFromJson(const nlohmann::json& 
json) {
+  // Handle boolean
+  if (json.is_boolean()) {
+    return json.get<bool>()
+               ? internal::checked_pointer_cast<Expression>(True::Instance())
+               : internal::checked_pointer_cast<Expression>(False::Instance());
+  }
+  return JsonParseError("Only booleans are currently supported.");
+}
+
+nlohmann::json ToJson(const Expression& expr) {
+  switch (expr.op()) {
+    case Expression::Operation::kTrue:
+      return true;
+
+    case Expression::Operation::kFalse:
+      return false;
+    default:
+      // TODO(evindj): This code will be removed as we implemented the full 
expression
+      // serialization.
+      ICEBERG_CHECK_OR_DIE(false, "Only booleans are currently supported.");
+  }
+}
+
+}  // namespace iceberg
diff --git a/src/iceberg/expression/json_serde_internal.h 
b/src/iceberg/expression/json_serde_internal.h
new file mode 100644
index 00000000..e44234d3
--- /dev/null
+++ b/src/iceberg/expression/json_serde_internal.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <nlohmann/json_fwd.hpp>
+
+#include "iceberg/expression/expression.h"
+#include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
+#include "iceberg/type_fwd.h"
+
+/// \file iceberg/expression/json_serde_internal.h
+/// JSON serialization and deserialization for expressions.
+
+namespace iceberg {
+
+/// \brief Converts an operation type string to an Expression::Operation.
+///
+/// \param typeStr The operation type string
+/// \return The corresponding Operation or an error if unknown
+ICEBERG_EXPORT Result<Expression::Operation> OperationTypeFromJson(
+    const nlohmann::json& json);
+
+/// \brief Converts an Expression::Operation to its json representation.
+///
+/// \param op The operation to convert
+/// \return The operation type string (e.g., "eq", "lt-eq", "is-null")
+ICEBERG_EXPORT nlohmann::json ToJson(Expression::Operation op);
+
+/// \brief Deserializes a JSON object into an Expression.
+///
+/// \param json A JSON object representing an expression
+/// \return A shared pointer to the deserialized Expression or an error
+ICEBERG_EXPORT Result<std::shared_ptr<Expression>> ExpressionFromJson(
+    const nlohmann::json& json);
+
+/// \brief Serializes an Expression into its JSON representation.
+///
+/// \param expr The expression to serialize
+/// \return A JSON object representing the expression
+ICEBERG_EXPORT nlohmann::json ToJson(const Expression& expr);
+
+/// Check if an operation is a unary predicate
+ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op);
+
+/// Check if an operation is a set predicate
+ICEBERG_EXPORT bool IsSetOperation(Expression::Operation op);
+
+}  // namespace iceberg
diff --git a/src/iceberg/meson.build b/src/iceberg/meson.build
index fd82a889..6c8adcf7 100644
--- a/src/iceberg/meson.build
+++ b/src/iceberg/meson.build
@@ -49,6 +49,7 @@ iceberg_sources = files(
     'expression/expression.cc',
     'expression/expressions.cc',
     'expression/inclusive_metrics_evaluator.cc',
+    'expression/json_serde.cc',
     'expression/literal.cc',
     'expression/manifest_evaluator.cc',
     'expression/predicate.cc',
diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt
index ae61d819..215d883b 100644
--- a/src/iceberg/test/CMakeLists.txt
+++ b/src/iceberg/test/CMakeLists.txt
@@ -88,6 +88,7 @@ add_iceberg_test(table_test
 add_iceberg_test(expression_test
                  SOURCES
                  aggregate_test.cc
+                 expression_json_test.cc
                  expression_test.cc
                  expression_visitor_test.cc
                  inclusive_metrics_evaluator_test.cc
diff --git a/src/iceberg/test/expression_json_test.cc 
b/src/iceberg/test/expression_json_test.cc
new file mode 100644
index 00000000..dd3ac5e3
--- /dev/null
+++ b/src/iceberg/test/expression_json_test.cc
@@ -0,0 +1,66 @@
+/*
+ * 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 <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nlohmann/json.hpp>
+
+#include "iceberg/expression/expression.h"
+#include "iceberg/expression/expressions.h"
+#include "iceberg/expression/json_serde_internal.h"
+#include "iceberg/expression/literal.h"
+#include "iceberg/expression/predicate.h"
+#include "iceberg/expression/term.h"
+#include "iceberg/test/matchers.h"
+
+namespace iceberg {
+
+// Test boolean constant expressions
+TEST(ExpressionJsonTest, CheckBooleanExpression) {
+  auto checkBoolean = [](std::shared_ptr<Expression> expr, bool value) {
+    auto json = ToJson(*expr);
+    EXPECT_TRUE(json.is_boolean());
+    EXPECT_EQ(json.get<bool>(), value);
+
+    auto result = ExpressionFromJson(json);
+    ASSERT_THAT(result, IsOk());
+    if (value) {
+      EXPECT_EQ(result.value()->op(), Expression::Operation::kTrue);
+    } else {
+      EXPECT_EQ(result.value()->op(), Expression::Operation::kFalse);
+    }
+  };
+  checkBoolean(True::Instance(), true);
+  checkBoolean(False::Instance(), false);
+}
+
+TEST(ExpressionJsonTest, OperationTypeTests) {
+  EXPECT_EQ(OperationTypeFromJson("true"), Expression::Operation::kTrue);
+  EXPECT_EQ("true", ToJson(Expression::Operation::kTrue));
+  EXPECT_TRUE(IsSetOperation(Expression::Operation::kIn));
+  EXPECT_FALSE(IsSetOperation(Expression::Operation::kTrue));
+
+  EXPECT_TRUE(IsUnaryOperation(Expression::Operation::kIsNull));
+  EXPECT_FALSE(IsUnaryOperation(Expression::Operation::kTrue));
+}
+
+}  // namespace iceberg
diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build
index bf73dd13..8b657f4e 100644
--- a/src/iceberg/test/meson.build
+++ b/src/iceberg/test/meson.build
@@ -61,6 +61,7 @@ iceberg_tests = {
     'expression_test': {
         'sources': files(
             'aggregate_test.cc',
+            'expression_json_test.cc',
             'expression_test.cc',
             'expression_visitor_test.cc',
             'inclusive_metrics_evaluator_test.cc',

Reply via email to