This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 2877be2b5 feat(c++): support variant-based union type serialization
for c++ (#3032)
2877be2b5 is described below
commit 2877be2b5a93be3227aa6e02932cc770610597fd
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Dec 9 21:35:20 2025 +0800
feat(c++): support variant-based union type serialization for c++ (#3032)
## Why?
## What does this PR do?
## Related issues
Closes #3025
#3027
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
cpp/fory/serialization/BUILD | 11 +
cpp/fory/serialization/CMakeLists.txt | 7 +
cpp/fory/serialization/fory.h | 1 +
cpp/fory/serialization/serializer_traits.h | 15 ++
cpp/fory/serialization/skip.cc | 32 +++
cpp/fory/serialization/skip.h | 3 +
cpp/fory/serialization/variant_serializer.h | 256 ++++++++++++++++++++++
cpp/fory/serialization/variant_serializer_test.cc | 199 +++++++++++++++++
cpp/fory/type/type.h | 4 +
docs/specification/xlang_serialization_spec.md | 8 +-
docs/specification/xlang_type_mapping.md | 80 +++----
11 files changed, 574 insertions(+), 42 deletions(-)
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index 534613da0..fe51077c3 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -27,6 +27,7 @@ cc_library(
"type_info.h",
"type_resolver.h",
"unsigned_serializer.h",
+ "variant_serializer.h",
],
strip_include_prefix = "/cpp",
deps = [
@@ -99,6 +100,16 @@ cc_test(
],
)
+cc_test(
+ name = "variant_serializer_test",
+ srcs = ["variant_serializer_test.cc"],
+ deps = [
+ ":fory_serialization",
+ "@googletest//:gtest",
+ "@googletest//:gtest_main",
+ ],
+)
+
cc_test(
name = "tuple_serializer_test",
srcs = ["tuple_serializer_test.cc"],
diff --git a/cpp/fory/serialization/CMakeLists.txt
b/cpp/fory/serialization/CMakeLists.txt
index 0a752cc17..509b2f6ee 100644
--- a/cpp/fory/serialization/CMakeLists.txt
+++ b/cpp/fory/serialization/CMakeLists.txt
@@ -37,8 +37,11 @@ set(FORY_SERIALIZATION_HEADERS
smart_ptr_serializers.h
struct_serializer.h
temporal_serializers.h
+ tuple_serializer.h
type_info.h
type_resolver.h
+ unsigned_serializer.h
+ variant_serializer.h
)
add_library(fory_serialization ${FORY_SERIALIZATION_SOURCES})
@@ -85,6 +88,10 @@ if(FORY_BUILD_TESTS)
add_executable(fory_serialization_map_test map_serializer_test.cc)
target_link_libraries(fory_serialization_map_test fory_serialization
GTest::gtest GTest::gtest_main)
gtest_discover_tests(fory_serialization_map_test)
+
+ add_executable(fory_serialization_variant_test variant_serializer_test.cc)
+ target_link_libraries(fory_serialization_variant_test fory_serialization
GTest::gtest GTest::gtest_main)
+ gtest_discover_tests(fory_serialization_variant_test)
endif()
# xlang test binary
diff --git a/cpp/fory/serialization/fory.h b/cpp/fory/serialization/fory.h
index ba492b014..7b331a9cf 100644
--- a/cpp/fory/serialization/fory.h
+++ b/cpp/fory/serialization/fory.h
@@ -30,6 +30,7 @@
#include "fory/serialization/temporal_serializers.h"
#include "fory/serialization/tuple_serializer.h"
#include "fory/serialization/type_resolver.h"
+#include "fory/serialization/variant_serializer.h"
#include "fory/util/buffer.h"
#include "fory/util/error.h"
#include "fory/util/pool.h"
diff --git a/cpp/fory/serialization/serializer_traits.h
b/cpp/fory/serialization/serializer_traits.h
index ccee4e1e1..b1846b9fe 100644
--- a/cpp/fory/serialization/serializer_traits.h
+++ b/cpp/fory/serialization/serializer_traits.h
@@ -34,6 +34,7 @@
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
+#include <variant>
#include <vector>
namespace fory {
@@ -93,6 +94,14 @@ struct is_tuple<std::tuple<Ts...>> : std::true_type {};
template <typename T> inline constexpr bool is_tuple_v = is_tuple<T>::value;
+/// Detect std::variant
+template <typename T> struct is_variant : std::false_type {};
+
+template <typename... Ts>
+struct is_variant<std::variant<Ts...>> : std::true_type {};
+
+template <typename T> inline constexpr bool is_variant_v =
is_variant<T>::value;
+
/// Detect std::weak_ptr
template <typename T> struct is_weak_ptr : std::false_type {};
@@ -507,6 +516,12 @@ template <typename... Ts> struct
TypeIndex<std::tuple<Ts...>> {
fnv1a_64_combine(fnv1a_64("std::tuple"), (type_index<Ts>() ^ ...));
};
+// variant<Ts...>
+template <typename... Ts> struct TypeIndex<std::variant<Ts...>> {
+ static constexpr uint64_t value =
+ fnv1a_64_combine(fnv1a_64("std::variant"), (type_index<Ts>() ^ ...));
+};
+
// ============================================================================
// type_index<T>() function - main entry point
// ============================================================================
diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc
index b10f1441c..41c3e1d98 100644
--- a/cpp/fory/serialization/skip.cc
+++ b/cpp/fory/serialization/skip.cc
@@ -393,6 +393,30 @@ void skip_unknown(ReadContext &ctx) {
}
}
+void skip_union(ReadContext &ctx) {
+ // Read the variant index
+ (void)ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ // Read and skip the alternative's type info
+ const TypeInfo *type_info = ctx.read_any_typeinfo(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (!type_info) {
+ ctx.set_error(
+ Error::type_error("TypeInfo not found for UNION alternative skip"));
+ return;
+ }
+
+ // Skip the alternative's value
+ FieldType alt_field_type;
+ alt_field_type.type_id = type_info->type_id;
+ alt_field_type.nullable = false;
+ skip_field_value(ctx, alt_field_type, false);
+}
+
void skip_field_value(ReadContext &ctx, const FieldType &field_type,
bool read_ref_flag) {
// Read ref flag if needed
@@ -568,6 +592,14 @@ void skip_field_value(ReadContext &ctx, const FieldType
&field_type,
skip_unknown(ctx);
return;
+ case TypeId::UNION:
+ skip_union(ctx);
+ return;
+
+ case TypeId::NONE:
+ // NONE (not-applicable/monostate) has no data to skip
+ return;
+
default:
ctx.set_error(
Error::type_error("Unknown field type to skip: " +
diff --git a/cpp/fory/serialization/skip.h b/cpp/fory/serialization/skip.h
index f5d9c1bb8..64e730688 100644
--- a/cpp/fory/serialization/skip.h
+++ b/cpp/fory/serialization/skip.h
@@ -52,6 +52,9 @@ void skip_set(ReadContext &ctx, const FieldType &field_type);
/// Skip a map value
void skip_map(ReadContext &ctx, const FieldType &field_type);
+/// Skip a union (variant) value
+void skip_union(ReadContext &ctx);
+
/// Skip a struct value
void skip_struct(ReadContext &ctx, const FieldType &field_type);
diff --git a/cpp/fory/serialization/variant_serializer.h
b/cpp/fory/serialization/variant_serializer.h
new file mode 100644
index 000000000..b7c551f86
--- /dev/null
+++ b/cpp/fory/serialization/variant_serializer.h
@@ -0,0 +1,256 @@
+/*
+ * 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 "fory/serialization/serializer.h"
+#include "fory/type/type.h"
+#include "fory/util/error.h"
+#include <cstdint>
+#include <variant>
+
+namespace fory {
+namespace serialization {
+
+// ============================================================================
+// std::monostate Serializer
+// ============================================================================
+
+/// Serializer for std::monostate (empty variant alternative)
+///
+/// std::monostate represents an empty state in variants. It has no data
+/// to serialize, so serialization is a no-op.
+template <> struct Serializer<std::monostate> {
+ static constexpr TypeId type_id =
+ TypeId::NONE; // Use NONE for empty/not-applicable type
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ // Read and validate type_id for monostate
+ ctx.read_varuint32(ctx.error());
+ // Accept any type since monostate is just a marker with no data
+ }
+
+ static inline void write(const std::monostate &, WriteContext &ctx,
+ bool write_ref, bool write_type,
+ bool has_generics = false) {
+ (void)has_generics;
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ write_type_info(ctx);
+ }
+ // No data to write for monostate
+ }
+
+ static inline void write_data(const std::monostate &, WriteContext &ctx) {
+ // No data to write for monostate
+ }
+
+ static inline void write_data_generic(const std::monostate &,
+ WriteContext &ctx, bool has_generics) {
+ // No data to write for monostate
+ }
+
+ static inline std::monostate read(ReadContext &ctx, bool read_ref,
+ bool read_type) {
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (!has_value) {
+ return std::monostate{};
+ }
+ if (read_type) {
+ read_type_info(ctx);
+ }
+ return std::monostate{};
+ }
+
+ static inline std::monostate read_data(ReadContext &ctx) {
+ // No data to read for monostate
+ return std::monostate{};
+ }
+
+ static inline std::monostate read_with_type_info(ReadContext &ctx,
+ bool read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+// ============================================================================
+// std::variant Serialization Helpers
+// ============================================================================
+
+/// Helper to write variant data by dispatching to the correct alternative's
+/// serializer based on the active index.
+template <typename Variant, size_t Index = 0>
+inline void write_variant_by_index(const Variant &variant, WriteContext &ctx,
+ size_t active_index, bool write_type) {
+ if constexpr (Index < std::variant_size_v<Variant>) {
+ if (Index == active_index) {
+ using AlternativeType = std::variant_alternative_t<Index, Variant>;
+ const auto &value = std::get<Index>(variant);
+ Serializer<AlternativeType>::write(value, ctx, false, write_type);
+ } else {
+ write_variant_by_index<Variant, Index + 1>(variant, ctx, active_index,
+ write_type);
+ }
+ } else {
+ // Should never reach here if active_index is valid
+ ctx.set_error(Error::invalid_data("Invalid variant index: " +
+ std::to_string(active_index)));
+ }
+}
+
+/// Helper to read variant data by dispatching to the correct alternative's
+/// serializer based on the stored index.
+template <typename Variant, size_t Index = 0>
+inline Variant read_variant_by_index(ReadContext &ctx, size_t stored_index,
+ bool read_type) {
+ if constexpr (Index < std::variant_size_v<Variant>) {
+ if (Index == stored_index) {
+ using AlternativeType = std::variant_alternative_t<Index, Variant>;
+ AlternativeType value =
+ Serializer<AlternativeType>::read(ctx, false, read_type);
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ // Return a default-constructed variant with the first alternative
+ return Variant{};
+ }
+ return Variant{std::in_place_index<Index>, std::move(value)};
+ } else {
+ return read_variant_by_index<Variant, Index + 1>(ctx, stored_index,
+ read_type);
+ }
+ } else {
+ // Invalid index - set error and return default
+ ctx.set_error(Error::invalid_data("Invalid variant index during read: " +
+ std::to_string(stored_index)));
+ return Variant{};
+ }
+}
+
+// ============================================================================
+// std::variant Serializer
+// ============================================================================
+
+/// Serializer for std::variant<Ts...>
+///
+/// Serializes variant by storing:
+/// 1. The active alternative index (as varuint32)
+/// 2. The value of the active alternative
+///
+/// This allows the deserializer to determine which alternative to construct
+/// and forward to the appropriate serializer.
+///
+/// Example:
+/// ```cpp
+/// std::variant<int, std::string, double> v = "hello";
+/// // Serializes: index=1, then string data
+/// ```
+template <typename... Ts> struct Serializer<std::variant<Ts...>> {
+ // Use UNION type_id since variant is a sum type (union) that can hold
+ // one of several alternative types. The actual alternative type is
determined
+ // by the stored index.
+ static constexpr TypeId type_id = TypeId::UNION;
+
+ using VariantType = std::variant<Ts...>;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ // Write UNION type_id to indicate variant/sum type
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ // Read and validate UNION type_id
+ uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (!type_id_matches(type_id_read, static_cast<uint32_t>(type_id))) {
+ ctx.set_error(
+ Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id)));
+ }
+ }
+
+ static inline void write(const VariantType &variant, WriteContext &ctx,
+ bool write_ref, bool write_type,
+ bool has_generics = false) {
+ (void)has_generics;
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ write_type_info(ctx);
+ }
+ write_data(variant, ctx);
+ }
+
+ static inline void write_data(const VariantType &variant, WriteContext &ctx)
{
+ // Write the active variant index
+ size_t active_index = variant.index();
+ ctx.write_varuint32(static_cast<uint32_t>(active_index));
+
+ // Dispatch to the appropriate alternative's serializer
+ // In xlang/compatible mode, write type info for the alternative
+ bool write_alt_type = ctx.is_xlang() || ctx.is_compatible();
+ write_variant_by_index(variant, ctx, active_index, write_alt_type);
+ }
+
+ static inline void write_data_generic(const VariantType &variant,
+ WriteContext &ctx, bool has_generics) {
+ write_data(variant, ctx);
+ }
+
+ static inline VariantType read(ReadContext &ctx, bool read_ref,
+ bool read_type) {
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (!has_value) {
+ return VariantType{};
+ }
+ if (read_type) {
+ read_type_info(ctx);
+ }
+ return read_data(ctx);
+ }
+
+ static inline VariantType read_data(ReadContext &ctx) {
+ // Read the stored variant index
+ uint32_t stored_index = ctx.read_varuint32(ctx.error());
+ // Validate index is within bounds
+ if (FORY_PREDICT_FALSE(stored_index >= std::variant_size_v<VariantType>)) {
+ ctx.set_error(Error::invalid_data(
+ "Variant index out of bounds: " + std::to_string(stored_index) +
+ " (max: " + std::to_string(std::variant_size_v<VariantType> - 1) +
+ ")"));
+ return VariantType{};
+ }
+
+ // Dispatch to the appropriate alternative's serializer
+ // In xlang/compatible mode, read type info for the alternative
+ bool read_alt_type = ctx.is_xlang() || ctx.is_compatible();
+ return read_variant_by_index<VariantType>(ctx, stored_index,
read_alt_type);
+ }
+
+ static inline VariantType read_with_type_info(ReadContext &ctx, bool
read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/serialization/variant_serializer_test.cc
b/cpp/fory/serialization/variant_serializer_test.cc
new file mode 100644
index 000000000..1d0a408a6
--- /dev/null
+++ b/cpp/fory/serialization/variant_serializer_test.cc
@@ -0,0 +1,199 @@
+/*
+ * 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 "fory/serialization/fory.h"
+#include "gtest/gtest.h"
+#include <string>
+#include <variant>
+#include <vector>
+
+using namespace fory::serialization;
+
+// Structs for schema evolution testing
+namespace {
+using VariantType = std::variant<int, std::string>;
+
+// Old schema: struct with two variant fields
+struct OldStruct {
+ int id;
+ VariantType field1;
+ VariantType field2;
+};
+FORY_STRUCT(OldStruct, id, field1, field2);
+
+// New schema: struct with only one variant field
+struct NewStruct {
+ int id;
+ VariantType field1;
+};
+FORY_STRUCT(NewStruct, id, field1);
+} // namespace
+
+// Helper to create a Fory instance
+Fory create_fory() {
+ return Fory::builder().xlang(false).track_ref(true).build();
+}
+
+Fory create_xlang_fory() {
+ return Fory::builder().xlang(true).track_ref(true).build();
+}
+
+// Test basic variant serialization with primitive types
+TEST(VariantSerializerTest, BasicVariant) {
+ Fory fory = create_fory();
+
+ // Test with int active
+ {
+ std::variant<int, std::string, double> v1 = 42;
+ auto result1 = fory.serialize(v1);
+ ASSERT_TRUE(result1.has_value());
+
+ auto read_result1 =
+ fory.deserialize<std::variant<int, std::string, double>>(
+ result1.value());
+ ASSERT_TRUE(read_result1.has_value()) << read_result1.error().message();
+ ASSERT_EQ(read_result1.value().index(), 0);
+ ASSERT_EQ(std::get<0>(read_result1.value()), 42);
+ }
+
+ // Test with string active
+ {
+ std::variant<int, std::string, double> v2 = std::string("hello");
+ auto result2 = fory.serialize(v2);
+ ASSERT_TRUE(result2.has_value());
+
+ auto read_result2 =
+ fory.deserialize<std::variant<int, std::string, double>>(
+ result2.value());
+ ASSERT_TRUE(read_result2.has_value());
+ ASSERT_EQ(read_result2.value().index(), 1);
+ ASSERT_EQ(std::get<1>(read_result2.value()), "hello");
+ }
+
+ // Test with double active
+ {
+ std::variant<int, std::string, double> v3 = 3.14;
+ auto result3 = fory.serialize(v3);
+ ASSERT_TRUE(result3.has_value());
+
+ auto read_result3 =
+ fory.deserialize<std::variant<int, std::string, double>>(
+ result3.value());
+ ASSERT_TRUE(read_result3.has_value());
+ ASSERT_EQ(read_result3.value().index(), 2);
+ ASSERT_DOUBLE_EQ(std::get<2>(read_result3.value()), 3.14);
+ }
+}
+
+// Test variant with complex types
+TEST(VariantSerializerTest, ComplexVariant) {
+ Fory fory = create_fory();
+
+ std::variant<std::vector<int>, std::string> v1 =
+ std::vector<int>{1, 2, 3, 4, 5};
+ auto result = fory.serialize(v1);
+ ASSERT_TRUE(result.has_value());
+
+ auto read_result =
+ fory.deserialize<std::variant<std::vector<int>, std::string>>(
+ result.value());
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_EQ(read_result.value().index(), 0);
+
+ auto vec = std::get<0>(read_result.value());
+ ASSERT_EQ(vec.size(), 5);
+ ASSERT_EQ(vec[0], 1);
+ ASSERT_EQ(vec[4], 5);
+}
+
+// Test variant in xlang mode
+TEST(VariantSerializerTest, XlangMode) {
+ Fory fory = create_xlang_fory();
+
+ std::variant<int, std::string, bool> v = std::string("xlang");
+ auto result = fory.serialize(v);
+ ASSERT_TRUE(result.has_value());
+
+ auto read_result =
+ fory.deserialize<std::variant<int, std::string, bool>>(result.value());
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_EQ(read_result.value().index(), 1);
+ ASSERT_EQ(std::get<1>(read_result.value()), "xlang");
+}
+
+// Test variant with monostate (empty variant)
+TEST(VariantSerializerTest, MonostateVariant) {
+ Fory fory = create_fory();
+
+ std::variant<std::monostate, int, std::string> v1;
+ auto result = fory.serialize(v1);
+ ASSERT_TRUE(result.has_value());
+
+ auto read_result =
+ fory.deserialize<std::variant<std::monostate, int, std::string>>(
+ result.value());
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_EQ(read_result.value().index(), 0);
+ ASSERT_TRUE(std::holds_alternative<std::monostate>(read_result.value()));
+}
+
+// Test nested variants
+TEST(VariantSerializerTest, NestedVariant) {
+ Fory fory = create_fory();
+
+ using InnerVariant = std::variant<int, std::string>;
+ using OuterVariant = std::variant<InnerVariant, double>;
+
+ OuterVariant v = InnerVariant{std::string("nested")};
+ auto result = fory.serialize(v);
+ ASSERT_TRUE(result.has_value());
+
+ auto read_result = fory.deserialize<OuterVariant>(result.value());
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_EQ(read_result.value().index(), 0);
+
+ auto inner = std::get<0>(read_result.value());
+ ASSERT_EQ(inner.index(), 1);
+ ASSERT_EQ(std::get<1>(inner), "nested");
+}
+
+// Test that variant can be skipped in compatible mode for schema evolution
+TEST(VariantSerializerTest, SkipInCompatibleMode) {
+ constexpr uint32_t type_id = 1;
+
+ // Create first Fory instance and register OldStruct
+ auto fory1 = Fory::builder().compatible(true).xlang(true).build();
+ fory1.register_struct<OldStruct>(type_id);
+
+ // Serialize with old schema (two variants)
+ OldStruct old_data{42, std::string("hello"), 123};
+ auto result = fory1.serialize(old_data);
+ ASSERT_TRUE(result.has_value()) << result.error().message();
+
+ // Create second Fory instance and register NewStruct with same type_id
+ auto fory2 = Fory::builder().compatible(true).xlang(true).build();
+ fory2.register_struct<NewStruct>(type_id);
+
+ // Deserialize with new schema (one variant) - should skip field2
+ auto read_result = fory2.deserialize<NewStruct>(result.value());
+ ASSERT_TRUE(read_result.has_value()) << read_result.error().message();
+ ASSERT_EQ(read_result.value().id, 42);
+ ASSERT_EQ(read_result.value().field1.index(), 1);
+ ASSERT_EQ(std::get<1>(read_result.value().field1), "hello");
+}
diff --git a/cpp/fory/type/type.h b/cpp/fory/type/type.h
index d6221fbbb..9501e4fc4 100644
--- a/cpp/fory/type/type.h
+++ b/cpp/fory/type/type.h
@@ -103,6 +103,10 @@ enum class TypeId : int32_t {
FLOAT32_ARRAY = 36,
// one-dimensional float64 array.
FLOAT64_ARRAY = 37,
+ // an union type that can hold different types of values.
+ UNION = 38,
+ // a null value with no data.
+ NONE = 39,
// Unsigned integer types (native mode only, not supported in xlang mode)
// an 8-bit unsigned integer.
U8 = 64,
diff --git a/docs/specification/xlang_serialization_spec.md
b/docs/specification/xlang_serialization_spec.md
index 3f25b7197..0715c9c4f 100644
--- a/docs/specification/xlang_serialization_spec.md
+++ b/docs/specification/xlang_serialization_spec.md
@@ -60,7 +60,7 @@ This specification defines the Fory xlang binary format. The
format is dynamic r
- named_ext: an `ext` type whose type mapping will be encoded as a name.
- list: a sequence of objects.
- set: an unordered set of unique elements.
-- map: a map of key-value pairs. Mutable types such as
`list/map/set/array/tensor` are not allowed as key of map.
+- map: a map of key-value pairs. Mutable types such as `list/map/set/array`
are not allowed as key of map.
- duration: an absolute length of time, independent of any calendar/timezone,
as a count of nanoseconds.
- timestamp: a point in time, independent of any calendar/timezone, as a count
of nanoseconds. The count is relative
to an epoch at UTC midnight on January 1, 1970.
@@ -77,7 +77,8 @@ This specification defines the Fory xlang binary format. The
format is dynamic r
- float16_array: one dimensional half_float_16 array.
- float32_array: one dimensional float32 array.
- float64_array: one dimensional float64 array.
-- tensor: multidimensional array which every sub-array have same size and type.
+- union: a tagged union type that can hold one of several alternative types.
The active alternative is identified by an index.
+- none: represents an empty/unit value with no data (e.g., for empty union
alternatives).
Note:
@@ -186,7 +187,8 @@ custom types (struct/ext/enum). User type IDs are in a
separate namespace and co
| 35 | FLOAT16_ARRAY | 1D float16 array
|
| 36 | FLOAT32_ARRAY | 1D float32 array
|
| 37 | FLOAT64_ARRAY | 1D float64 array
|
-| 38 | TENSOR | Multi-dimensional array
|
+| 38 | UNION | Tagged union type (one of several
alternatives) |
+| 39 | NONE | Empty/unit type (no data)
|
#### Type ID Encoding for User Types
diff --git a/docs/specification/xlang_type_mapping.md
b/docs/specification/xlang_type_mapping.md
index a3b8a70ab..1661d0b77 100644
--- a/docs/specification/xlang_type_mapping.md
+++ b/docs/specification/xlang_type_mapping.md
@@ -27,45 +27,47 @@ Note:
## Type Mapping
-| Fory Type | Fory Type ID | Java | Python
| Javascript | C++ | Golang
| Rust |
-| ----------------------- | ------------ | --------------- |
--------------------------------- | --------------- |
------------------------------ | ---------------- | ---------------- |
-| bool | 1 | bool/Boolean | bool
| Boolean | bool | bool
| bool |
-| int8 | 2 | byte/Byte | int/pyfory.Int8
| Type.int8() | int8_t | int8
| i8 |
-| int16 | 3 | short/Short | int/pyfory.Int16
| Type.int16() | int16_t | int16
| i6 |
-| int32 | 4 | int/Integer | int/pyfory.Int32
| Type.int32() | int32_t | int32
| i32 |
-| var_int32 | 5 | int/Integer |
int/pyfory.VarInt32 | Type.varint32() | fory::varint32_t
| fory.varint32 | fory::varint32 |
-| int64 | 6 | long/Long | int/pyfory.Int64
| Type.int64() | int64_t | int64
| i64 |
-| var_int64 | 7 | long/Long |
int/pyfory.VarInt64 | Type.varint64() | fory::varint64_t
| fory.varint64 | fory::varint64 |
-| sli_int64 | 8 | long/Long |
int/pyfory.SliInt64 | Type.sliint64() | fory::sliint64_t
| fory.sliint64 | fory::sliint64 |
-| float16 | 9 | float/Float |
float/pyfory.Float16 | Type.float16() | fory::float16_t
| fory.float16 | fory::f16 |
-| float32 | 10 | float/Float |
float/pyfory.Float32 | Type.float32() | float
| float32 | f32 |
-| float64 | 11 | double/Double |
float/pyfory.Float64 | Type.float64() | double
| float64 | f64 |
-| string | 12 | String | str
| String | string | string
| String/str |
-| enum | 13 | Enum subclasses | enum subclasses
| / | enum | /
| enum |
-| named_enum | 14 | Enum subclasses | enum subclasses
| / | enum | /
| enum |
-| struct | 15 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| compatible_struct | 16 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| named_struct | 17 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| named_compatible_struct | 18 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| ext | 19 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| named_ext | 20 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
-| list | 21 | List/Collection | list/tuple
| array | vector | slice
| Vec |
-| set | 22 | Set | set
| / | set | fory.Set
| Set |
-| map | 23 | Map | dict
| Map | unordered_map | map
| HashMap |
-| duration | 24 | Duration | timedelta
| Number | duration | Duration
| Duration |
-| timestamp | 25 | Instant | datetime
| Number | std::chrono::nanoseconds | Time
| DateTime |
-| local_date | 26 | Date | datetime
| Number | std::chrono::nanoseconds | Time
| DateTime |
-| decimal | 27 | BigDecimal | Decimal
| bigint | / | /
| / |
-| binary | 28 | byte[] | bytes
| / | `uint8_t[n]/vector<T>` |
`[n]uint8/[]T` | `Vec<uint8_t>` |
-| array | 29 | array | np.ndarray
| / | / |
array/slice | Vec |
-| bool_array | 30 | bool[] |
ndarray(np.bool\_) | / | `bool[n]`
| `[n]bool/[]T` | `Vec<bool>` |
-| int8_array | 31 | byte[] | ndarray(int8)
| / | `int8_t[n]/vector<T>` |
`[n]int8/[]T` | `Vec<i18>` |
-| int16_array | 32 | short[] | ndarray(int16)
| / | `int16_t[n]/vector<T>` |
`[n]int16/[]T` | `Vec<i16>` |
-| int32_array | 33 | int[] | ndarray(int32)
| / | `int32_t[n]/vector<T>` |
`[n]int32/[]T` | `Vec<i32>` |
-| int64_array | 34 | long[] | ndarray(int64)
| / | `int64_t[n]/vector<T>` |
`[n]int64/[]T` | `Vec<i64>` |
-| float16_array | 35 | float[] | ndarray(float16)
| / | `fory::float16_t[n]/vector<T>` |
`[n]float16/[]T` | `Vec<fory::f16>` |
-| float32_array | 36 | float[] | ndarray(float32)
| / | `float[n]/vector<T>` |
`[n]float32/[]T` | `Vec<f32>` |
-| float64_array | 37 | double[] | ndarray(float64)
| / | `double[n]/vector<T>` |
`[n]float64/[]T` | `Vec<f64>` |
+| Fory Type | Fory Type ID | Java | Python
| Javascript | C++ | Golang
| Rust |
+| ----------------------- | ------------ | --------------- |
--------------------------------- | --------------- |
------------------------------ | ---------------- | ----------------- |
+| bool | 1 | bool/Boolean | bool
| Boolean | bool | bool
| bool |
+| int8 | 2 | byte/Byte | int/pyfory.Int8
| Type.int8() | int8_t | int8
| i8 |
+| int16 | 3 | short/Short | int/pyfory.Int16
| Type.int16() | int16_t | int16
| i6 |
+| int32 | 4 | int/Integer | int/pyfory.Int32
| Type.int32() | int32_t | int32
| i32 |
+| var_int32 | 5 | int/Integer |
int/pyfory.VarInt32 | Type.varint32() | fory::varint32_t
| fory.varint32 | fory::varint32 |
+| int64 | 6 | long/Long | int/pyfory.Int64
| Type.int64() | int64_t | int64
| i64 |
+| var_int64 | 7 | long/Long |
int/pyfory.VarInt64 | Type.varint64() | fory::varint64_t
| fory.varint64 | fory::varint64 |
+| sli_int64 | 8 | long/Long |
int/pyfory.SliInt64 | Type.sliint64() | fory::sliint64_t
| fory.sliint64 | fory::sliint64 |
+| float16 | 9 | float/Float |
float/pyfory.Float16 | Type.float16() | fory::float16_t
| fory.float16 | fory::f16 |
+| float32 | 10 | float/Float |
float/pyfory.Float32 | Type.float32() | float
| float32 | f32 |
+| float64 | 11 | double/Double |
float/pyfory.Float64 | Type.float64() | double
| float64 | f64 |
+| string | 12 | String | str
| String | string | string
| String/str |
+| enum | 13 | Enum subclasses | enum subclasses
| / | enum | /
| enum |
+| named_enum | 14 | Enum subclasses | enum subclasses
| / | enum | /
| enum |
+| struct | 15 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| compatible_struct | 16 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| named_struct | 17 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| named_compatible_struct | 18 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| ext | 19 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| named_ext | 20 | pojo/record | data class / type
with type hints | object | struct/class | struct
| struct |
+| list | 21 | List/Collection | list/tuple
| array | vector | slice
| Vec |
+| set | 22 | Set | set
| / | set | fory.Set
| Set |
+| map | 23 | Map | dict
| Map | unordered_map | map
| HashMap |
+| duration | 24 | Duration | timedelta
| Number | duration | Duration
| Duration |
+| timestamp | 25 | Instant | datetime
| Number | std::chrono::nanoseconds | Time
| DateTime |
+| local_date | 26 | Date | datetime
| Number | std::chrono::nanoseconds | Time
| DateTime |
+| decimal | 27 | BigDecimal | Decimal
| bigint | / | /
| / |
+| binary | 28 | byte[] | bytes
| / | `uint8_t[n]/vector<T>` |
`[n]uint8/[]T` | `Vec<uint8_t>` |
+| array | 29 | array | np.ndarray
| / | / |
array/slice | Vec |
+| bool_array | 30 | bool[] |
ndarray(np.bool\_) | / | `bool[n]`
| `[n]bool/[]T` | `Vec<bool>` |
+| int8_array | 31 | byte[] | ndarray(int8)
| / | `int8_t[n]/vector<T>` |
`[n]int8/[]T` | `Vec<i18>` |
+| int16_array | 32 | short[] | ndarray(int16)
| / | `int16_t[n]/vector<T>` |
`[n]int16/[]T` | `Vec<i16>` |
+| int32_array | 33 | int[] | ndarray(int32)
| / | `int32_t[n]/vector<T>` |
`[n]int32/[]T` | `Vec<i32>` |
+| int64_array | 34 | long[] | ndarray(int64)
| / | `int64_t[n]/vector<T>` |
`[n]int64/[]T` | `Vec<i64>` |
+| float16_array | 35 | float[] | ndarray(float16)
| / | `fory::float16_t[n]/vector<T>` |
`[n]float16/[]T` | `Vec<fory::f16>` |
+| float32_array | 36 | float[] | ndarray(float32)
| / | `float[n]/vector<T>` |
`[n]float32/[]T` | `Vec<f32>` |
+| float64_array | 37 | double[] | ndarray(float64)
| / | `double[n]/vector<T>` |
`[n]float64/[]T` | `Vec<f64>` |
+| union | 38 | / | /
| / | `std::variant<Ts...>` | /
| tagged union enum |
+| none | 39 | null | None
| null | `std::monostate` | nil
| `()` |
## Type info(not implemented currently)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]