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 cf747e89b feat(c++): add polymorphic serialization support for `any` 
to compiler (#3232)
cf747e89b is described below

commit cf747e89bd16c1cd74cde4a5ebb361005d6d8742
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jan 28 02:18:41 2026 +0800

    feat(c++): add polymorphic serialization support for `any` to compiler 
(#3232)
    
    ## Why?
    
    
    
    
    ## What does this PR do?
    
    - Add `any` as a primitive in the compiler IR and proto frontend
    (including `google.protobuf.Any`), and generate language bindings across
    C++/Go/Java/Python/Rust with nullable handling.
    - Introduce C++ `std::any` serialization support plus validator and
    resolver updates needed for `any`/union handling in xlang.
    - Add `any` IDL/proto examples, cross-language roundtrip coverage, and
    docs updates.
    
    
    ## Related issues
    
    Closes #3172
    Closes #3231
    
    #3099
    
    
    ## 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
---
 compiler/fory_compiler/frontend/proto/parser.py    |   5 +-
 .../fory_compiler/frontend/proto/translator.py     |   1 +
 compiler/fory_compiler/generators/cpp.py           |  26 ++-
 compiler/fory_compiler/generators/go.py            |  18 +-
 compiler/fory_compiler/generators/java.py          |  25 ++-
 compiler/fory_compiler/generators/python.py        |  17 +-
 compiler/fory_compiler/generators/rust.py          |  42 ++++-
 compiler/fory_compiler/ir/types.py                 |   2 +
 compiler/fory_compiler/ir/validator.py             |  34 ++++
 cpp/fory/serialization/BUILD                       |  11 ++
 cpp/fory/serialization/CMakeLists.txt              |   5 +
 cpp/fory/serialization/any_serializer.h            | 163 ++++++++++++++++
 cpp/fory/serialization/any_serializer_test.cc      |  97 ++++++++++
 cpp/fory/serialization/fory.h                      |   1 +
 cpp/fory/serialization/type_info.h                 |   5 +
 cpp/fory/serialization/type_resolver.cc            |   1 +
 cpp/fory/serialization/type_resolver.h             |  89 +++++++--
 docs/compiler/fdl-syntax.md                        |   7 +
 docs/compiler/protobuf-idl.md                      |   2 +-
 docs/compiler/type-system.md                       |  23 +++
 integration_tests/idl_tests/cpp/main.cc            |  52 ++++++
 integration_tests/idl_tests/generate_idl.py        |   4 +
 .../idl_tests/go/idl_roundtrip_test.go             | 205 +++++++++++++++++++++
 .../{rust/src/lib.rs => idl/any_example.fdl}       |  28 ++-
 integration_tests/idl_tests/idl/any_example.proto  |  50 +++++
 .../apache/fory/idl_tests/IdlRoundTripTest.java    |  33 ++++
 .../idl_tests/python/src/idl_tests/roundtrip.py    |  27 +++
 integration_tests/idl_tests/rust/src/lib.rs        |   1 +
 .../idl_tests/rust/tests/idl_roundtrip.rs          |  93 ++++++++++
 .../org/apache/fory/resolver/TypeResolver.java     |   8 +
 30 files changed, 1040 insertions(+), 35 deletions(-)

diff --git a/compiler/fory_compiler/frontend/proto/parser.py 
b/compiler/fory_compiler/frontend/proto/parser.py
index 4bf74db25..127a5cad0 100644
--- a/compiler/fory_compiler/frontend/proto/parser.py
+++ b/compiler/fory_compiler/frontend/proto/parser.py
@@ -104,7 +104,10 @@ class Parser:
                     raise self.error("Duplicate package declaration")
                 package = self.parse_package()
             elif self.check(TokenType.IMPORT):
-                imports.append(self.parse_import())
+                path = self.parse_import()
+                normalized_path = path.lstrip("/")
+                if not normalized_path.startswith("google/protobuf/"):
+                    imports.append(path)
             elif self.check(TokenType.OPTION):
                 name, value = self.parse_option_statement()
                 options[name] = value
diff --git a/compiler/fory_compiler/frontend/proto/translator.py 
b/compiler/fory_compiler/frontend/proto/translator.py
index d1f0e6d5c..46f5eeddd 100644
--- a/compiler/fory_compiler/frontend/proto/translator.py
+++ b/compiler/fory_compiler/frontend/proto/translator.py
@@ -74,6 +74,7 @@ class ProtoTranslator:
     WELL_KNOWN_TYPES: Dict[str, PrimitiveKind] = {
         "google.protobuf.Timestamp": PrimitiveKind.TIMESTAMP,
         "google.protobuf.Duration": PrimitiveKind.DURATION,
+        "google.protobuf.Any": PrimitiveKind.ANY,
     }
 
     TYPE_OVERRIDES: Dict[str, PrimitiveKind] = {
diff --git a/compiler/fory_compiler/generators/cpp.py 
b/compiler/fory_compiler/generators/cpp.py
index 89ee65b8e..746371515 100644
--- a/compiler/fory_compiler/generators/cpp.py
+++ b/compiler/fory_compiler/generators/cpp.py
@@ -65,6 +65,7 @@ class CppGenerator(BaseGenerator):
         PrimitiveKind.BYTES: "std::vector<uint8_t>",
         PrimitiveKind.DATE: "fory::serialization::Date",
         PrimitiveKind.TIMESTAMP: "fory::serialization::Timestamp",
+        PrimitiveKind.ANY: "std::any",
     }
     NUMERIC_PRIMITIVES = {
         PrimitiveKind.BOOL,
@@ -544,6 +545,13 @@ class CppGenerator(BaseGenerator):
     ) -> str:
         member_name = self.get_field_member_name(field)
         other_member = f"other.{member_name}"
+        if isinstance(field.field_type, PrimitiveType) and (
+            field.field_type.kind == PrimitiveKind.ANY
+        ):
+            return (
+                f"((!{member_name}.has_value() && !{other_member}.has_value()) 
|| "
+                f"({member_name}.type() == {other_member}.type()))"
+            )
         if self.is_message_type(
             field.field_type, parent_stack
         ) and self.get_field_weak_ref(field):
@@ -1274,6 +1282,8 @@ class CppGenerator(BaseGenerator):
     ) -> str:
         """Generate C++ type string with package namespace."""
         if isinstance(field_type, PrimitiveType):
+            if field_type.kind == PrimitiveKind.ANY:
+                return self.PRIMITIVE_MAP[field_type.kind]
             base_type = self.PRIMITIVE_MAP[field_type.kind]
             if nullable:
                 return f"std::optional<{base_type}>"
@@ -1357,9 +1367,13 @@ class CppGenerator(BaseGenerator):
     def get_field_meta(self, field: Field) -> str:
         """Build FieldMeta expression for a field."""
         meta = "fory::F()"
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
         if field.tag_id is not None:
             meta += f".id({field.tag_id})"
-        if field.optional:
+        if field.optional or is_any:
             meta += ".nullable()"
         if field.ref:
             meta += ".ref()"
@@ -1374,7 +1388,11 @@ class CppGenerator(BaseGenerator):
     def get_union_field_meta(self, field: Field) -> str:
         """Build FieldMeta expression for a union case."""
         meta = f"fory::F({field.number})"
-        if field.optional:
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
+        if field.optional or is_any:
             meta += ".nullable()"
         if field.ref:
             meta += ".ref()"
@@ -1438,6 +1456,8 @@ class CppGenerator(BaseGenerator):
     ) -> str:
         """Generate C++ type string."""
         if isinstance(field_type, PrimitiveType):
+            if field_type.kind == PrimitiveKind.ANY:
+                return self.PRIMITIVE_MAP[field_type.kind]
             base_type = self.PRIMITIVE_MAP[field_type.kind]
             if nullable:
                 return f"std::optional<{base_type}>"
@@ -1568,6 +1588,8 @@ class CppGenerator(BaseGenerator):
                 includes.add("<vector>")
             elif field_type.kind in (PrimitiveKind.DATE, 
PrimitiveKind.TIMESTAMP):
                 includes.add('"fory/serialization/temporal_serializers.h"')
+            elif field_type.kind == PrimitiveKind.ANY:
+                includes.add("<any>")
 
         elif isinstance(field_type, ListType):
             includes.add("<vector>")
diff --git a/compiler/fory_compiler/generators/go.py 
b/compiler/fory_compiler/generators/go.py
index c4dac3e0c..c2d5abb9a 100644
--- a/compiler/fory_compiler/generators/go.py
+++ b/compiler/fory_compiler/generators/go.py
@@ -184,6 +184,7 @@ class GoGenerator(BaseGenerator):
         PrimitiveKind.BYTES: "[]byte",
         PrimitiveKind.DATE: "fory.Date",
         PrimitiveKind.TIMESTAMP: "time.Time",
+        PrimitiveKind.ANY: "any",
     }
 
     def generate(self) -> List[GeneratedFile]:
@@ -462,6 +463,7 @@ class GoGenerator(BaseGenerator):
                 PrimitiveKind.BYTES: "fory.BINARY",
                 PrimitiveKind.DATE: "fory.DATE",
                 PrimitiveKind.TIMESTAMP: "fory.TIMESTAMP",
+                PrimitiveKind.ANY: "fory.UNKNOWN",
             }
             return primitive_type_ids.get(kind, "fory.UNKNOWN")
         if isinstance(field.field_type, ListType):
@@ -473,15 +475,15 @@ class GoGenerator(BaseGenerator):
             if isinstance(type_def, Enum):
                 if type_def.type_id is None:
                     return "fory.NAMED_ENUM"
-                return f"({type_def.type_id} << 8) | fory.ENUM"
+                return "fory.ENUM"
             if isinstance(type_def, Union):
                 if type_def.type_id is None:
                     return "fory.NAMED_UNION"
-                return f"({type_def.type_id} << 8) | fory.UNION"
+                return "fory.UNION"
             if isinstance(type_def, Message):
                 if type_def.type_id is None:
                     return "fory.NAMED_STRUCT"
-                return f"({type_def.type_id} << 8) | fory.STRUCT"
+                return "fory.STRUCT"
         return "fory.UNKNOWN"
 
     def get_union_case_reflect_type_expr(
@@ -605,11 +607,15 @@ class GoGenerator(BaseGenerator):
         is_list = isinstance(field.field_type, ListType)
         is_map = isinstance(field.field_type, MapType)
         is_collection = is_list or is_map
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
         nullable_tag: Optional[bool] = None
         ref_tag: Optional[bool] = None
         if field.tag_id is not None:
             tags.append(f"id={field.tag_id}")
-        if field.optional:
+        if field.optional or is_any:
             nullable_tag = True
         elif is_collection and (
             field.ref or (is_list and (field.element_optional or 
field.element_ref))
@@ -682,6 +688,8 @@ class GoGenerator(BaseGenerator):
         if not field.optional or field.ref:
             return False
         if isinstance(field.field_type, PrimitiveType):
+            if field.field_type.kind == PrimitiveKind.ANY:
+                return False
             base_type = self.PRIMITIVE_MAP[field.field_type.kind]
             return base_type not in ("[]byte", "time.Time", "fory.Date")
         if isinstance(field.field_type, NamedType):
@@ -701,6 +709,8 @@ class GoGenerator(BaseGenerator):
     ) -> str:
         """Generate Go type string."""
         if isinstance(field_type, PrimitiveType):
+            if field_type.kind == PrimitiveKind.ANY:
+                return "any"
             base_type = self.PRIMITIVE_MAP[field_type.kind]
             if nullable and base_type not in ("[]byte",):
                 if (
diff --git a/compiler/fory_compiler/generators/java.py 
b/compiler/fory_compiler/generators/java.py
index b005da4d2..ecbf6584a 100644
--- a/compiler/fory_compiler/generators/java.py
+++ b/compiler/fory_compiler/generators/java.py
@@ -98,6 +98,7 @@ class JavaGenerator(BaseGenerator):
         PrimitiveKind.TIMESTAMP: "java.time.Instant",
         PrimitiveKind.DURATION: "java.time.Duration",
         PrimitiveKind.DECIMAL: "java.math.BigDecimal",
+        PrimitiveKind.ANY: "Object",
     }
 
     # Boxed versions for nullable primitives
@@ -120,6 +121,7 @@ class JavaGenerator(BaseGenerator):
         PrimitiveKind.FLOAT16: "Float",
         PrimitiveKind.FLOAT32: "Float",
         PrimitiveKind.FLOAT64: "Double",
+        PrimitiveKind.ANY: "Object",
     }
 
     # Primitive array types for repeated numeric fields
@@ -647,6 +649,7 @@ class JavaGenerator(BaseGenerator):
                 PrimitiveKind.BYTES: "Types.BINARY",
                 PrimitiveKind.DATE: "Types.DATE",
                 PrimitiveKind.TIMESTAMP: "Types.TIMESTAMP",
+                PrimitiveKind.ANY: "Types.UNKNOWN",
             }
             return primitive_type_ids.get(kind, "Types.UNKNOWN")
         if isinstance(field.field_type, ListType):
@@ -791,9 +794,14 @@ class JavaGenerator(BaseGenerator):
 
         # Generate @ForyField annotation if needed
         annotations = []
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
+        nullable = field.optional or is_any
         if field.tag_id is not None:
             annotations.append(f"id = {field.tag_id}")
-        if field.optional:
+        if nullable:
             annotations.append("nullable = true")
         if field.ref:
             annotations.append("ref = true")
@@ -812,7 +820,7 @@ class JavaGenerator(BaseGenerator):
         # Field type
         java_type = self.generate_type(
             field.field_type,
-            field.optional,
+            nullable,
             field.element_optional,
             field.element_ref,
         )
@@ -825,9 +833,14 @@ class JavaGenerator(BaseGenerator):
     def generate_getter_setter(self, field: Field) -> List[str]:
         """Generate getter and setter for a field."""
         lines = []
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
+        nullable = field.optional or is_any
         java_type = self.generate_type(
             field.field_type,
-            field.optional,
+            nullable,
             field.element_optional,
             field.element_ref,
         )
@@ -916,6 +929,10 @@ class JavaGenerator(BaseGenerator):
 
     def collect_field_imports(self, field: Field, imports: Set[str]):
         """Collect imports for a field, including list modifiers."""
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
         self.collect_type_imports(
             field.field_type,
             imports,
@@ -924,7 +941,7 @@ class JavaGenerator(BaseGenerator):
         )
         self.collect_integer_imports(field.field_type, imports)
         self.collect_array_imports(field, imports)
-        if field.optional or field.ref or field.tag_id is not None:
+        if field.optional or field.ref or field.tag_id is not None or is_any:
             imports.add("org.apache.fory.annotation.ForyField")
 
     def collect_array_imports(self, field: Field, imports: Set[str]) -> None:
diff --git a/compiler/fory_compiler/generators/python.py 
b/compiler/fory_compiler/generators/python.py
index 7f559aa8c..8ed180333 100644
--- a/compiler/fory_compiler/generators/python.py
+++ b/compiler/fory_compiler/generators/python.py
@@ -65,6 +65,7 @@ class PythonGenerator(BaseGenerator):
         PrimitiveKind.BYTES: "bytes",
         PrimitiveKind.DATE: "datetime.date",
         PrimitiveKind.TIMESTAMP: "datetime.datetime",
+        PrimitiveKind.ANY: "Any",
     }
 
     # Numpy dtype strings for primitive arrays
@@ -134,6 +135,7 @@ class PythonGenerator(BaseGenerator):
         PrimitiveKind.BYTES: 'b""',
         PrimitiveKind.DATE: "None",
         PrimitiveKind.TIMESTAMP: "None",
+        PrimitiveKind.ANY: "None",
     }
 
     def safe_name(self, name: str) -> str:
@@ -461,9 +463,14 @@ class PythonGenerator(BaseGenerator):
         """Generate a dataclass field."""
         lines = []
 
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
+        nullable = field.optional or is_any
         python_type = self.generate_type(
             field.field_type,
-            field.optional,
+            nullable,
             field.element_optional,
             parent_stack,
         )
@@ -477,11 +484,11 @@ class PythonGenerator(BaseGenerator):
             trailing_comment = f"  # {comment}"
 
         tag_id = field.tag_id
-        if tag_id is not None or field.ref:
+        if tag_id is not None or field.ref or nullable:
             field_args = []
             if tag_id is not None:
                 field_args.append(f"id={tag_id}")
-            if field.optional:
+            if nullable:
                 field_args.append("nullable=True")
             if field.ref:
                 field_args.append("ref=True")
@@ -530,6 +537,8 @@ class PythonGenerator(BaseGenerator):
     ) -> str:
         """Generate Python type hint."""
         if isinstance(field_type, PrimitiveType):
+            if field_type.kind == PrimitiveKind.ANY:
+                return "Any"
             base_type = self.PRIMITIVE_MAP[field_type.kind]
             if nullable:
                 return f"Optional[{base_type}]"
@@ -685,6 +694,8 @@ class PythonGenerator(BaseGenerator):
         if isinstance(field_type, PrimitiveType):
             if field_type.kind in (PrimitiveKind.DATE, 
PrimitiveKind.TIMESTAMP):
                 imports.add("import datetime")
+            elif field_type.kind == PrimitiveKind.ANY:
+                imports.add("from typing import Any")
 
         elif isinstance(field_type, ListType):
             # Add numpy import for primitive arrays
diff --git a/compiler/fory_compiler/generators/rust.py 
b/compiler/fory_compiler/generators/rust.py
index fa0e72635..d4c9f2652 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -64,6 +64,7 @@ class RustGenerator(BaseGenerator):
         PrimitiveKind.BYTES: "Vec<u8>",
         PrimitiveKind.DATE: "chrono::NaiveDate",
         PrimitiveKind.TIMESTAMP: "chrono::NaiveDateTime",
+        PrimitiveKind.ANY: "Box<dyn Any>",
     }
 
     def generate(self) -> List[GeneratedFile]:
@@ -233,7 +234,13 @@ class RustGenerator(BaseGenerator):
         """Generate a Rust tagged union."""
         lines: List[str] = []
 
-        lines.append("#[derive(ForyObject, Debug, Clone, PartialEq)]")
+        has_any = any(
+            self.field_type_has_any(field.field_type) for field in union.fields
+        )
+        derives = ["ForyObject", "Debug"]
+        if not has_any:
+            derives.extend(["Clone", "PartialEq"])
+        lines.append(f"#[derive({', '.join(derives)})]")
         lines.append(f"pub enum {union.name} {{")
 
         for field in union.fields:
@@ -275,7 +282,10 @@ class RustGenerator(BaseGenerator):
         type_name = message.name
 
         # Derive macros
-        lines.append("#[derive(ForyObject, Debug, Clone, PartialEq, Default)]")
+        derives = ["ForyObject", "Debug"]
+        if not self.message_has_any(message):
+            derives.extend(["Clone", "PartialEq", "Default"])
+        lines.append(f"#[derive({', '.join(derives)})]")
 
         lines.append(f"pub struct {type_name} {{")
 
@@ -290,6 +300,24 @@ class RustGenerator(BaseGenerator):
 
         return lines
 
+    def message_has_any(self, message: Message) -> bool:
+        """Return True if a message contains any type fields."""
+        return any(
+            self.field_type_has_any(field.field_type) for field in 
message.fields
+        )
+
+    def field_type_has_any(self, field_type: FieldType) -> bool:
+        """Return True if field type or its children is any."""
+        if isinstance(field_type, PrimitiveType):
+            return field_type.kind == PrimitiveKind.ANY
+        if isinstance(field_type, ListType):
+            return self.field_type_has_any(field_type.element_type)
+        if isinstance(field_type, MapType):
+            return self.field_type_has_any(
+                field_type.key_type
+            ) or self.field_type_has_any(field_type.value_type)
+        return False
+
     def generate_nested_module(
         self,
         message: Message,
@@ -354,7 +382,11 @@ class RustGenerator(BaseGenerator):
         attrs = []
         if field.tag_id is not None:
             attrs.append(f"id = {field.tag_id}")
-        if field.optional:
+        is_any = (
+            isinstance(field.field_type, PrimitiveType)
+            and field.field_type.kind == PrimitiveKind.ANY
+        )
+        if field.optional or is_any:
             attrs.append("nullable = true")
         if field.ref:
             attrs.append("ref = true")
@@ -454,6 +486,8 @@ class RustGenerator(BaseGenerator):
     ) -> str:
         """Generate Rust type string."""
         if isinstance(field_type, PrimitiveType):
+            if field_type.kind == PrimitiveKind.ANY:
+                return "Box<dyn Any>"
             base_type = self.PRIMITIVE_MAP[field_type.kind]
             if nullable:
                 return f"Option<{base_type}>"
@@ -538,6 +572,8 @@ class RustGenerator(BaseGenerator):
         if isinstance(field_type, PrimitiveType):
             if field_type.kind in (PrimitiveKind.DATE, 
PrimitiveKind.TIMESTAMP):
                 uses.add("use chrono")
+            if field_type.kind == PrimitiveKind.ANY:
+                uses.add("use std::any::Any")
 
         elif isinstance(field_type, NamedType):
             pass  # No additional uses needed
diff --git a/compiler/fory_compiler/ir/types.py 
b/compiler/fory_compiler/ir/types.py
index 46d96d4ed..3dfc3d8ed 100644
--- a/compiler/fory_compiler/ir/types.py
+++ b/compiler/fory_compiler/ir/types.py
@@ -47,6 +47,7 @@ class PrimitiveKind(PyEnum):
     TIMESTAMP = "timestamp"
     DURATION = "duration"
     DECIMAL = "decimal"
+    ANY = "any"
 
 
 PRIMITIVE_TYPES = {
@@ -74,6 +75,7 @@ PRIMITIVE_TYPES = {
     "timestamp": PrimitiveKind.TIMESTAMP,
     "duration": PrimitiveKind.DURATION,
     "decimal": PrimitiveKind.DECIMAL,
+    "any": PrimitiveKind.ANY,
 }
 
 
diff --git a/compiler/fory_compiler/ir/validator.py 
b/compiler/fory_compiler/ir/validator.py
index a6e07998d..0f0683147 100644
--- a/compiler/fory_compiler/ir/validator.py
+++ b/compiler/fory_compiler/ir/validator.py
@@ -27,11 +27,13 @@ from fory_compiler.ir.ast import (
     Union,
     Field,
     FieldType,
+    PrimitiveType,
     NamedType,
     ListType,
     MapType,
     SourceLocation,
 )
+from fory_compiler.ir.types import PrimitiveKind
 
 
 @dataclass
@@ -313,6 +315,12 @@ class SchemaValidator:
                 check_type_ref(f.field_type, f, None)
 
     def _check_ref_rules(self) -> None:
+        def is_any_type(field_type: FieldType) -> bool:
+            return (
+                isinstance(field_type, PrimitiveType)
+                and field_type.kind == PrimitiveKind.ANY
+            )
+
         def resolve_target(
             target: NamedType,
             enclosing_messages: Optional[List[Message]],
@@ -344,6 +352,32 @@ class SchemaValidator:
             field: Field,
             enclosing_messages: Optional[List[Message]] = None,
         ) -> None:
+            if is_any_type(field.field_type) and field.ref:
+                self._error(
+                    "ref is not allowed on any fields",
+                    field.location,
+                )
+
+            if (
+                isinstance(field.field_type, ListType)
+                and is_any_type(field.field_type.element_type)
+                and field.element_ref
+            ):
+                self._error(
+                    "ref is not allowed on repeated any fields",
+                    field.location,
+                )
+
+            if (
+                isinstance(field.field_type, MapType)
+                and is_any_type(field.field_type.value_type)
+                and field.field_type.value_ref
+            ):
+                self._error(
+                    "ref is not allowed on map values of any type",
+                    field.location,
+                )
+
             if field.ref:
                 if isinstance(field.field_type, (ListType, MapType)):
                     self._error(
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index 34a447018..5f737ae5f 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -8,6 +8,7 @@ cc_library(
         "skip.cc",
     ],
     hdrs = [
+        "any_serializer.h",
         "array_serializer.h",
         "basic_serializer.h",
         "collection_serializer.h",
@@ -134,6 +135,16 @@ cc_test(
     ],
 )
 
+cc_test(
+    name = "any_serializer_test",
+    srcs = ["any_serializer_test.cc"],
+    deps = [
+        ":fory_serialization",
+        "@googletest//:gtest",
+        "@googletest//:gtest_main",
+    ],
+)
+
 cc_test(
     name = "field_serializer_test",
     srcs = ["field_serializer_test.cc"],
diff --git a/cpp/fory/serialization/CMakeLists.txt 
b/cpp/fory/serialization/CMakeLists.txt
index 7f5f01d3a..d4852742e 100644
--- a/cpp/fory/serialization/CMakeLists.txt
+++ b/cpp/fory/serialization/CMakeLists.txt
@@ -22,6 +22,7 @@ set(FORY_SERIALIZATION_SOURCES
 )
 
 set(FORY_SERIALIZATION_HEADERS
+    any_serializer.h
     array_serializer.h
     basic_serializer.h
     collection_serializer.h
@@ -108,6 +109,10 @@ if(FORY_BUILD_TESTS)
     add_executable(fory_serialization_weak_ptr_test 
weak_ptr_serializer_test.cc)
     target_link_libraries(fory_serialization_weak_ptr_test fory_serialization 
GTest::gtest GTest::gtest_main)
     gtest_discover_tests(fory_serialization_weak_ptr_test)
+
+    add_executable(fory_serialization_any_test any_serializer_test.cc)
+    target_link_libraries(fory_serialization_any_test fory_serialization 
GTest::gtest GTest::gtest_main)
+    gtest_discover_tests(fory_serialization_any_test)
 endif()
 
 # xlang test binary
diff --git a/cpp/fory/serialization/any_serializer.h 
b/cpp/fory/serialization/any_serializer.h
new file mode 100644
index 000000000..7a10dd6a1
--- /dev/null
+++ b/cpp/fory/serialization/any_serializer.h
@@ -0,0 +1,163 @@
+/*
+ * 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/serialization/type_info.h"
+#include "fory/serialization/type_resolver.h"
+#include "fory/type/type.h"
+#include "fory/util/error.h"
+#include "fory/util/result.h"
+
+#include <any>
+#include <typeindex>
+
+namespace fory {
+namespace serialization {
+
+// ============================================================================
+// std::any Serializer
+// ============================================================================
+
+/// Serializer for std::any.
+///
+/// std::any serialization requires explicit registration of allowed value
+/// types via register_any_type<T>(). Only registered types can be serialized
+/// or deserialized.
+template <> struct Serializer<std::any> {
+  static constexpr TypeId type_id = TypeId::UNKNOWN;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.set_error(Error::invalid("std::any requires runtime type info"));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    ctx.set_error(Error::invalid("std::any requires runtime type info"));
+  }
+
+  static inline void write(const std::any &value, WriteContext &ctx,
+                           RefMode ref_mode, bool write_type,
+                           bool has_generics = false) {
+    (void)has_generics;
+    if (ref_mode != RefMode::None) {
+      if (!value.has_value()) {
+        ctx.write_int8(NULL_FLAG);
+        return;
+      }
+      write_not_null_ref_flag(ctx, ref_mode);
+    } else if (FORY_PREDICT_FALSE(!value.has_value())) {
+      ctx.set_error(Error::invalid("std::any requires non-empty value"));
+      return;
+    }
+
+    const std::type_index concrete_type_id(value.type());
+    auto type_info_res = ctx.type_resolver().get_type_info(concrete_type_id);
+    if (FORY_PREDICT_FALSE(!type_info_res.ok())) {
+      ctx.set_error(Error::type_error("std::any type is not registered"));
+      return;
+    }
+    const TypeInfo *type_info = type_info_res.value();
+    if (FORY_PREDICT_FALSE(type_info->harness.any_write_fn == nullptr)) {
+      ctx.set_error(Error::type_error("std::any type is not registered"));
+      return;
+    }
+
+    if (write_type) {
+      uint32_t fory_type_id = type_info->type_id;
+      uint32_t type_id_arg = is_internal_type(fory_type_id)
+                                 ? fory_type_id
+                                 : static_cast<uint32_t>(TypeId::UNKNOWN);
+      auto write_res = ctx.write_any_typeinfo(type_id_arg, concrete_type_id);
+      if (FORY_PREDICT_FALSE(!write_res.ok())) {
+        ctx.set_error(std::move(write_res).error());
+        return;
+      }
+    }
+
+    type_info->harness.any_write_fn(value, ctx);
+  }
+
+  static inline void write_data(const std::any &, WriteContext &ctx) {
+    ctx.set_error(Error::invalid("std::any requires type info for writing"));
+  }
+
+  static inline void write_data_generic(const std::any &value,
+                                        WriteContext &ctx, bool has_generics) {
+    (void)has_generics;
+    write_data(value, ctx);
+  }
+
+  static inline std::any read(ReadContext &ctx, RefMode ref_mode,
+                              bool read_type) {
+    bool has_value = read_null_only_flag(ctx, ref_mode);
+    if (ctx.has_error() || !has_value) {
+      return std::any();
+    }
+    if (FORY_PREDICT_FALSE(!read_type)) {
+      ctx.set_error(Error::invalid("std::any requires read_type=true"));
+      return std::any();
+    }
+
+    const TypeInfo *type_info = ctx.read_any_typeinfo(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::any();
+    }
+
+    if (FORY_PREDICT_FALSE(type_info->harness.any_read_fn == nullptr)) {
+      ctx.set_error(Error::type_error("std::any type is not registered"));
+      return std::any();
+    }
+
+    return type_info->harness.any_read_fn(ctx);
+  }
+
+  static inline std::any read_data(ReadContext &ctx) {
+    ctx.set_error(Error::invalid("std::any requires type info for reading"));
+    return std::any();
+  }
+
+  static inline std::any read_with_type_info(ReadContext &ctx, RefMode 
ref_mode,
+                                             const TypeInfo &type_info) {
+    bool has_value = read_null_only_flag(ctx, ref_mode);
+    if (ctx.has_error() || !has_value) {
+      return std::any();
+    }
+
+    if (FORY_PREDICT_FALSE(type_info.harness.any_read_fn == nullptr)) {
+      ctx.set_error(Error::type_error("std::any type is not registered"));
+      return std::any();
+    }
+
+    return type_info.harness.any_read_fn(ctx);
+  }
+};
+
+/// Register a type so it can be serialized inside std::any.
+///
+/// For internal types (primitives, string, temporal), registration does not
+/// require prior type registration. For user-defined types, the type must be
+/// registered with TypeResolver first (e.g., via Fory::register_struct).
+template <typename T>
+Result<void, Error> register_any_type(TypeResolver &resolver) {
+  return resolver.template register_any_type<T>();
+}
+
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/serialization/any_serializer_test.cc 
b/cpp/fory/serialization/any_serializer_test.cc
new file mode 100644
index 000000000..0135b2517
--- /dev/null
+++ b/cpp/fory/serialization/any_serializer_test.cc
@@ -0,0 +1,97 @@
+/*
+ * 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/any_serializer.h"
+#include "fory/serialization/fory.h"
+#include "gtest/gtest.h"
+
+#include <any>
+#include <string>
+
+namespace fory {
+namespace serialization {
+namespace test {
+
+struct AnyInnerStruct {
+  int32_t id;
+  std::string name;
+
+  bool operator==(const AnyInnerStruct &other) const {
+    return id == other.id && name == other.name;
+  }
+
+  FORY_STRUCT(AnyInnerStruct, id, name);
+};
+
+inline bool any_equals(const std::any &left, const std::any &right) {
+  if (left.type() != right.type()) {
+    return false;
+  }
+  if (left.type() == typeid(std::string)) {
+    return std::any_cast<const std::string &>(left) ==
+           std::any_cast<const std::string &>(right);
+  }
+  if (left.type() == typeid(AnyInnerStruct)) {
+    return std::any_cast<const AnyInnerStruct &>(left) ==
+           std::any_cast<const AnyInnerStruct &>(right);
+  }
+  return false;
+}
+
+struct AnyHolderStruct {
+  std::any first;
+  std::any second;
+
+  bool operator==(const AnyHolderStruct &other) const {
+    return any_equals(first, other.first) && any_equals(second, other.second);
+  }
+
+  FORY_STRUCT(AnyHolderStruct, first, second);
+};
+
+TEST(AnySerializerTest, RoundTripStructFields) {
+  auto fory = Fory::builder().xlang(true).track_ref(false).build();
+
+  ASSERT_TRUE(fory.register_struct<AnyInnerStruct>(1).ok());
+  ASSERT_TRUE(fory.register_struct<AnyHolderStruct>(2).ok());
+
+  ASSERT_TRUE(register_any_type<std::string>(fory.type_resolver()).ok());
+  ASSERT_TRUE(register_any_type<AnyInnerStruct>(fory.type_resolver()).ok());
+
+  AnyHolderStruct original;
+  original.first = std::string("hello any");
+  original.second = AnyInnerStruct{42, "nested"};
+
+  auto serialize_result = fory.serialize(original);
+  ASSERT_TRUE(serialize_result.ok())
+      << "Serialization failed: " << serialize_result.error().to_string();
+
+  std::vector<uint8_t> bytes = std::move(serialize_result).value();
+  auto deserialize_result =
+      fory.deserialize<AnyHolderStruct>(bytes.data(), bytes.size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << "Deserialization failed: " << deserialize_result.error().to_string();
+
+  AnyHolderStruct deserialized = std::move(deserialize_result).value();
+  EXPECT_EQ(original, deserialized);
+}
+
+} // namespace test
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/serialization/fory.h b/cpp/fory/serialization/fory.h
index 496a8e62e..29217dde5 100644
--- a/cpp/fory/serialization/fory.h
+++ b/cpp/fory/serialization/fory.h
@@ -19,6 +19,7 @@
 
 #pragma once
 
+#include "fory/serialization/any_serializer.h"
 #include "fory/serialization/array_serializer.h"
 #include "fory/serialization/collection_serializer.h"
 #include "fory/serialization/config.h"
diff --git a/cpp/fory/serialization/type_info.h 
b/cpp/fory/serialization/type_info.h
index 12a811029..dc6565df8 100644
--- a/cpp/fory/serialization/type_info.h
+++ b/cpp/fory/serialization/type_info.h
@@ -25,6 +25,7 @@
 
 #include "absl/container/flat_hash_map.h"
 
+#include <any>
 #include <cstdint>
 #include <memory>
 #include <string>
@@ -65,6 +66,8 @@ struct Harness {
                                      const struct TypeInfo *type_info);
   using SortedFieldInfosFn =
       Result<std::vector<FieldInfo>, Error> (*)(TypeResolver &);
+  using AnyWriteFn = void (*)(const std::any &value, WriteContext &ctx);
+  using AnyReadFn = std::any (*)(ReadContext &ctx);
 
   Harness() = default;
   Harness(WriteFn write, ReadFn read, WriteDataFn write_data,
@@ -86,6 +89,8 @@ struct Harness {
   ReadDataFn read_data_fn = nullptr;
   SortedFieldInfosFn sorted_field_infos_fn = nullptr;
   ReadCompatibleFn read_compatible_fn = nullptr;
+  AnyWriteFn any_write_fn = nullptr;
+  AnyReadFn any_read_fn = nullptr;
 };
 
 // ============================================================================
diff --git a/cpp/fory/serialization/type_resolver.cc 
b/cpp/fory/serialization/type_resolver.cc
index 2f240feb2..491fb0155 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -1220,6 +1220,7 @@ TypeResolver::build_final_type_resolver() {
   for (const auto &[key, old_ptr] : type_info_by_runtime_type_) {
     final_resolver->type_info_by_runtime_type_[key] = ptr_map[old_ptr];
   }
+
   for (const auto &[key, old_ptr] : partial_type_infos_) {
     final_resolver->partial_type_infos_.put(key, ptr_map[old_ptr]);
   }
diff --git a/cpp/fory/serialization/type_resolver.h 
b/cpp/fory/serialization/type_resolver.h
index 6df14e00d..ed0a92be7 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -753,6 +753,8 @@ public:
   /// @return const pointer to TypeInfo if found, error otherwise
   template <typename T> Result<const TypeInfo *, Error> get_type_info() const;
 
+  template <typename T> Result<void, Error> register_any_type();
+
   /// Builds the final TypeResolver by completing all partial type infos
   /// created during registration.
   ///
@@ -923,6 +925,40 @@ inline void TypeResolver::check_registration_thread() {
       << "TypeResolver has been finalized, cannot register more types";
 }
 
+namespace detail {
+
+template <typename T>
+inline void any_write_adapter(const std::any &value, WriteContext &ctx) {
+  const T *ptr = std::any_cast<T>(&value);
+  if (ptr != nullptr) {
+    Serializer<T>::write_data(*ptr, ctx);
+    return;
+  }
+  const auto *shared_ptr = std::any_cast<std::shared_ptr<T>>(&value);
+  if (shared_ptr != nullptr) {
+    if (FORY_PREDICT_FALSE(!(*shared_ptr))) {
+      ctx.set_error(Error::invalid("std::any stored shared_ptr is null"));
+      return;
+    }
+    Serializer<T>::write_data(**shared_ptr, ctx);
+    return;
+  }
+  ctx.set_error(Error::type_error("std::any stored value type mismatch"));
+}
+
+template <typename T> inline std::any any_read_adapter(ReadContext &ctx) {
+  T value = Serializer<T>::read_data(ctx);
+  if (FORY_PREDICT_FALSE(ctx.has_error())) {
+    return std::any();
+  }
+  if constexpr (std::is_copy_constructible<T>::value) {
+    return std::any(std::move(value));
+  }
+  return std::any(std::make_shared<T>(std::move(value)));
+}
+
+} // namespace detail
+
 template <typename T> const TypeMeta &TypeResolver::struct_meta() {
   constexpr uint64_t ctid = type_index<T>();
   TypeInfo *info = type_info_by_ctid_.get_or_default(ctid, nullptr);
@@ -960,6 +996,31 @@ Result<const TypeInfo *, Error> 
TypeResolver::get_type_info() const {
   return Unexpected(Error::type_error("Type not registered"));
 }
 
+template <typename T> Result<void, Error> TypeResolver::register_any_type() {
+  check_registration_thread();
+  constexpr uint32_t static_type_id =
+      static_cast<uint32_t>(Serializer<T>::type_id);
+  TypeInfo *type_info = nullptr;
+  if (is_internal_type(static_type_id)) {
+    type_info = type_info_by_id_.get_or_default(static_type_id, nullptr);
+    if (FORY_PREDICT_FALSE(type_info == nullptr)) {
+      return Unexpected(Error::type_error("TypeInfo not found for type_id: " +
+                                          std::to_string(static_type_id)));
+    }
+  } else {
+    constexpr uint64_t ctid = type_index<T>();
+    type_info = type_info_by_ctid_.get_or_default(ctid, nullptr);
+    if (FORY_PREDICT_FALSE(type_info == nullptr)) {
+      return Unexpected(Error::type_error("Type not registered"));
+    }
+  }
+
+  type_info->harness.any_write_fn = &detail::any_write_adapter<T>;
+  type_info->harness.any_read_fn = &detail::any_read_adapter<T>;
+  register_type_internal_runtime(std::type_index(typeid(T)), type_info);
+  return Result<void, Error>();
+}
+
 template <typename T>
 Result<void, Error> TypeResolver::register_by_id(uint32_t type_id) {
   check_registration_thread();
@@ -1331,20 +1392,26 @@ TypeResolver::build_union_type_info(uint32_t type_id, 
std::string ns,
 }
 
 template <typename T> Harness TypeResolver::make_struct_harness() {
-  return Harness(&TypeResolver::harness_write_adapter<T>,
-                 &TypeResolver::harness_read_adapter<T>,
-                 &TypeResolver::harness_write_data_adapter<T>,
-                 &TypeResolver::harness_read_data_adapter<T>,
-                 &TypeResolver::harness_struct_sorted_fields<T>,
-                 &TypeResolver::harness_read_compatible_adapter<T>);
+  Harness harness(&TypeResolver::harness_write_adapter<T>,
+                  &TypeResolver::harness_read_adapter<T>,
+                  &TypeResolver::harness_write_data_adapter<T>,
+                  &TypeResolver::harness_read_data_adapter<T>,
+                  &TypeResolver::harness_struct_sorted_fields<T>,
+                  &TypeResolver::harness_read_compatible_adapter<T>);
+  harness.any_write_fn = &detail::any_write_adapter<T>;
+  harness.any_read_fn = &detail::any_read_adapter<T>;
+  return harness;
 }
 
 template <typename T> Harness TypeResolver::make_serializer_harness() {
-  return Harness(&TypeResolver::harness_write_adapter<T>,
-                 &TypeResolver::harness_read_adapter<T>,
-                 &TypeResolver::harness_write_data_adapter<T>,
-                 &TypeResolver::harness_read_data_adapter<T>,
-                 &TypeResolver::harness_empty_sorted_fields<T>);
+  Harness harness(&TypeResolver::harness_write_adapter<T>,
+                  &TypeResolver::harness_read_adapter<T>,
+                  &TypeResolver::harness_write_data_adapter<T>,
+                  &TypeResolver::harness_read_data_adapter<T>,
+                  &TypeResolver::harness_empty_sorted_fields<T>);
+  harness.any_write_fn = &detail::any_write_adapter<T>;
+  harness.any_read_fn = &detail::any_read_adapter<T>;
+  return harness;
 }
 
 template <typename T>
diff --git a/docs/compiler/fdl-syntax.md b/docs/compiler/fdl-syntax.md
index c8d5707f9..2a3c1e115 100644
--- a/docs/compiler/fdl-syntax.md
+++ b/docs/compiler/fdl-syntax.md
@@ -858,6 +858,7 @@ Modifiers before `repeated` apply to the field/collection. 
Modifiers after
 | `timestamp`     | Date and time with timezone               | Variable |
 | `duration`      | Duration                                  | Variable |
 | `decimal`       | Decimal value                             | Variable |
+| `any`           | Dynamic value (runtime type)              | Variable |
 
 See [Type System](type-system.md) for complete type mappings.
 
@@ -867,6 +868,11 @@ See [Type System](type-system.md) for complete type 
mappings.
 - Use `fixed_*` for fixed-width integer encoding.
 - Use `tagged_*` for tagged/hybrid encoding (64-bit only).
 
+**Any type notes:**
+
+- `any` always writes a null flag (same as `nullable`) because the value may 
be empty.
+- `ref` is not allowed on `any` fields. Wrap `any` in a message if you need 
reference tracking.
+
 ### Named Types
 
 Reference other messages or enums by name:
@@ -1241,6 +1247,7 @@ primitive_type := 'bool'
                | 'float16' | 'float32' | 'float64'
                | 'string' | 'bytes'
                | 'date' | 'timestamp' | 'duration' | 'decimal'
+               | 'any'
 named_type   := qualified_name
 qualified_name := IDENTIFIER ('.' IDENTIFIER)*   // e.g., Parent.Child
 map_type     := 'map' '<' field_type ',' field_type '>'
diff --git a/docs/compiler/protobuf-idl.md b/docs/compiler/protobuf-idl.md
index 0038ffcd4..589d33c39 100644
--- a/docs/compiler/protobuf-idl.md
+++ b/docs/compiler/protobuf-idl.md
@@ -219,7 +219,7 @@ message TreeNode {
 | Map        | `map<K, V>`                                                     
                                       | `map<K, V>`                            
                                                         |
 | Nullable   | `optional T` (proto3)                                           
                                       | `optional T`                           
                                                         |
 | Oneof      | `oneof`                                                         
                                       | `union` (case id = field number)       
                                                         |
-| Any        | `google.protobuf.Any`                                           
                                       | Not supported                          
                                                         |
+| Any        | `google.protobuf.Any`                                           
                                       | `any`                                  
                                                         |
 | Extensions | `extend`                                                        
                                       | Not supported                          
                                                         |
 
 ### Wire Format
diff --git a/docs/compiler/type-system.md b/docs/compiler/type-system.md
index 8cdfbfbcf..484c421c7 100644
--- a/docs/compiler/type-system.md
+++ b/docs/compiler/type-system.md
@@ -215,6 +215,29 @@ timestamp created_at = 1;
 | Rust     | `chrono::NaiveDateTime`          | Requires `chrono` crate |
 | C++      | `fory::serialization::Timestamp` |                         |
 
+### Any
+
+Dynamic value with runtime type information:
+
+```protobuf
+any payload = 1;
+```
+
+| Language | Type           | Notes                |
+| -------- | -------------- | -------------------- |
+| Java     | `Object`       | Runtime type written |
+| Python   | `Any`          | Runtime type written |
+| Go       | `any`          | Runtime type written |
+| Rust     | `Box<dyn Any>` | Runtime type written |
+| C++      | `std::any`     | Runtime type written |
+
+**Notes:**
+
+- `any` always writes a null flag (same as `nullable`) because values may be 
empty; codegen treats `any` as nullable even without `optional`.
+- Allowed runtime values are limited to `bool`, `string`, `enum`, `message`, 
and `union`. Other primitives (numeric, bytes, date/time) and list/map are not 
supported; wrap them in a message or use explicit fields instead.
+- `ref` is not allowed on `any` fields (including repeated/map values). Wrap 
`any` in a message if you need reference tracking.
+- The runtime type must be registered in the target language schema/IDL 
registration; unknown types fail to deserialize.
+
 ## Enum Types
 
 Enums define named integer constants:
diff --git a/integration_tests/idl_tests/cpp/main.cc 
b/integration_tests/idl_tests/cpp/main.cc
index 631dfa67e..2227440d2 100644
--- a/integration_tests/idl_tests/cpp/main.cc
+++ b/integration_tests/idl_tests/cpp/main.cc
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+#include <any>
 #include <chrono>
 #include <cstdlib>
 #include <fstream>
@@ -27,7 +28,9 @@
 #include <vector>
 
 #include "addressbook.h"
+#include "any_example.h"
 #include "complex_fbs.h"
+#include "fory/serialization/any_serializer.h"
 #include "fory/serialization/fory.h"
 #include "graph.h"
 #include "monster.h"
@@ -151,6 +154,8 @@ fory::Result<void, fory::Error> ValidateGraph(const 
graph::Graph &graph_value) {
   return fory::Result<void, fory::Error>();
 }
 
+using StringMap = std::map<std::string, std::string>;
+
 fory::Result<void, fory::Error> RunRoundTrip() {
   auto fory = fory::serialization::Fory::builder()
                   .xlang(true)
@@ -162,6 +167,29 @@ fory::Result<void, fory::Error> RunRoundTrip() {
   monster::RegisterTypes(fory);
   complex_fbs::RegisterTypes(fory);
   optional_types::RegisterTypes(fory);
+  any_example::RegisterTypes(fory);
+
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<bool>(fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(fory::serialization::register_any_type<std::string>(
+      fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<fory::serialization::Date>(
+          fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<fory::serialization::Timestamp>(
+          fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<any_example::AnyInner>(
+          fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<any_example::AnyUnion>(
+          fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<std::vector<std::string>>(
+          fory.type_resolver()));
+  FORY_RETURN_IF_ERROR(
+      fory::serialization::register_any_type<StringMap>(fory.type_resolver()));
 
   addressbook::Person::PhoneNumber mobile;
   mobile.set_number("555-0100");
@@ -342,6 +370,30 @@ fory::Result<void, fory::Error> RunRoundTrip() {
         fory::Error::invalid("optional types roundtrip mismatch"));
   }
 
+  any_example::AnyInner any_inner;
+  any_inner.set_name("inner");
+
+  any_example::AnyHolder any_holder;
+  any_holder.set_bool_value(std::any(true));
+  any_holder.set_string_value(std::any(std::string("hello")));
+  any_holder.set_date_value(std::any(fory::serialization::Date(19724)));
+  any_holder.set_timestamp_value(std::any(
+      fory::serialization::Timestamp(std::chrono::seconds(1704164645))));
+  any_holder.set_message_value(std::any(any_inner));
+  any_holder.set_union_value(std::any(any_example::AnyUnion::text("union")));
+  any_holder.set_list_value(
+      std::any(std::vector<std::string>{"alpha", "beta"}));
+  any_holder.set_map_value(std::any(StringMap{{"k1", "v1"}, {"k2", "v2"}}));
+
+  FORY_TRY(any_bytes, fory.serialize(any_holder));
+  FORY_TRY(any_roundtrip, fory.deserialize<any_example::AnyHolder>(
+                              any_bytes.data(), any_bytes.size()));
+
+  if (!(any_roundtrip == any_holder)) {
+    return fory::Unexpected(
+        fory::Error::invalid("any holder roundtrip mismatch"));
+  }
+
   const char *data_file = std::getenv("DATA_FILE");
   if (data_file != nullptr && data_file[0] != '\0') {
     FORY_TRY(payload, ReadFile(data_file));
diff --git a/integration_tests/idl_tests/generate_idl.py 
b/integration_tests/idl_tests/generate_idl.py
index 52e6f29ae..8efdb6ac0 100755
--- a/integration_tests/idl_tests/generate_idl.py
+++ b/integration_tests/idl_tests/generate_idl.py
@@ -29,6 +29,8 @@ SCHEMAS = [
     IDL_DIR / "idl" / "optional_types.fdl",
     IDL_DIR / "idl" / "tree.fdl",
     IDL_DIR / "idl" / "graph.fdl",
+    IDL_DIR / "idl" / "any_example.fdl",
+    IDL_DIR / "idl" / "any_example.proto",
     IDL_DIR / "idl" / "monster.fbs",
     IDL_DIR / "idl" / "complex_fbs.fbs",
 ]
@@ -47,6 +49,8 @@ GO_OUTPUT_OVERRIDES = {
     "optional_types.fdl": IDL_DIR / "go" / "optional_types",
     "tree.fdl": IDL_DIR / "go" / "tree",
     "graph.fdl": IDL_DIR / "go" / "graph",
+    "any_example.fdl": IDL_DIR / "go" / "any_example",
+    "any_example.proto": IDL_DIR / "go" / "any_example_pb",
 }
 
 
diff --git a/integration_tests/idl_tests/go/idl_roundtrip_test.go 
b/integration_tests/idl_tests/go/idl_roundtrip_test.go
index 4d3a25bf4..fe3fb7742 100644
--- a/integration_tests/idl_tests/go/idl_roundtrip_test.go
+++ b/integration_tests/idl_tests/go/idl_roundtrip_test.go
@@ -25,6 +25,7 @@ import (
 
        fory "github.com/apache/fory/go/fory"
        "github.com/apache/fory/go/fory/optional"
+       anyexample 
"github.com/apache/fory/integration_tests/idl_tests/go/any_example"
        complexfbs 
"github.com/apache/fory/integration_tests/idl_tests/go/complex_fbs"
        graphpkg "github.com/apache/fory/integration_tests/idl_tests/go/graph"
        monster "github.com/apache/fory/integration_tests/idl_tests/go/monster"
@@ -82,6 +83,9 @@ func TestAddressBookRoundTrip(t *testing.T) {
        if err := optionaltypes.RegisterTypes(f); err != nil {
                t.Fatalf("register optional types: %v", err)
        }
+       if err := anyexample.RegisterTypes(f); err != nil {
+               t.Fatalf("register any example types: %v", err)
+       }
 
        book := buildAddressBook()
        runLocalRoundTrip(t, f, book)
@@ -103,6 +107,9 @@ func TestAddressBookRoundTrip(t *testing.T) {
        runLocalOptionalRoundTrip(t, f, holder)
        runFileOptionalRoundTrip(t, f, holder)
 
+       anyHolder := buildAnyHolder()
+       runLocalAnyRoundTrip(t, f, anyHolder)
+
        refFory := fory.NewFory(fory.WithXlang(true), 
fory.WithRefTracking(true))
        if err := treepkg.RegisterTypes(refFory); err != nil {
                t.Fatalf("register tree types: %v", err)
@@ -118,6 +125,204 @@ func TestAddressBookRoundTrip(t *testing.T) {
        runFileGraphRoundTrip(t, refFory, graphValue)
 }
 
+func buildAnyHolder() anyexample.AnyHolder {
+       inner := anyexample.AnyInner{Name: "inner"}
+       unionValue := anyexample.TextAnyUnion("union")
+       return anyexample.AnyHolder{
+               BoolValue:      true,
+               StringValue:    "hello",
+               DateValue:      fory.Date{Year: 2024, Month: time.January, Day: 
2},
+               TimestampValue: time.Unix(1704164645, 0).UTC(),
+               MessageValue:   &inner,
+               UnionValue:     unionValue,
+               ListValue:      []string{"alpha", "beta"},
+               MapValue:       map[string]string{"k1": "v1", "k2": "v2"},
+       }
+}
+
+func runLocalAnyRoundTrip(t *testing.T, f *fory.Fory, holder 
anyexample.AnyHolder) {
+       data, err := f.Serialize(&holder)
+       if err != nil {
+               t.Fatalf("serialize any: %v", err)
+       }
+
+       var out anyexample.AnyHolder
+       if err := f.Deserialize(data, &out); err != nil {
+               t.Fatalf("deserialize any: %v", err)
+       }
+
+       assertAnyHolderEqual(t, holder, out)
+}
+
+func assertAnyHolderEqual(t *testing.T, expected, actual anyexample.AnyHolder) 
{
+       t.Helper()
+
+       if !anyBoolEqual(expected.BoolValue, actual.BoolValue) {
+               t.Fatalf("any bool mismatch: %#v != %#v", expected.BoolValue, 
actual.BoolValue)
+       }
+       if !anyStringEqual(expected.StringValue, actual.StringValue) {
+               t.Fatalf("any string mismatch: %#v != %#v", 
expected.StringValue, actual.StringValue)
+       }
+       if !anyDateEqual(expected.DateValue, actual.DateValue) {
+               t.Fatalf("any date mismatch: %#v != %#v", expected.DateValue, 
actual.DateValue)
+       }
+       if !anyTimeEqual(expected.TimestampValue, actual.TimestampValue) {
+               t.Fatalf("any timestamp mismatch: %#v != %#v", 
expected.TimestampValue, actual.TimestampValue)
+       }
+       if !anyInnerEqual(expected.MessageValue, actual.MessageValue) {
+               t.Fatalf("any message mismatch: %#v != %#v", 
expected.MessageValue, actual.MessageValue)
+       }
+       if !reflect.DeepEqual(expected.UnionValue, actual.UnionValue) {
+               t.Fatalf("any union mismatch: %#v != %#v", expected.UnionValue, 
actual.UnionValue)
+       }
+       if !anyStringSliceEqual(expected.ListValue, actual.ListValue) {
+               t.Fatalf("any list mismatch: %#v != %#v", expected.ListValue, 
actual.ListValue)
+       }
+       if !anyStringMapEqual(expected.MapValue, actual.MapValue) {
+               t.Fatalf("any map mismatch: %#v != %#v", expected.MapValue, 
actual.MapValue)
+       }
+}
+
+func anyBoolEqual(expected, actual any) bool {
+       expectedValue, ok := expected.(bool)
+       if !ok {
+               return false
+       }
+       actualValue, ok := actual.(bool)
+       if !ok {
+               return false
+       }
+       return expectedValue == actualValue
+}
+
+func anyStringEqual(expected, actual any) bool {
+       expectedValue, ok := expected.(string)
+       if !ok {
+               return false
+       }
+       actualValue, ok := actual.(string)
+       if !ok {
+               return false
+       }
+       return expectedValue == actualValue
+}
+
+func anyDateEqual(expected, actual any) bool {
+       expectedValue, ok := expected.(fory.Date)
+       if !ok {
+               return false
+       }
+       actualValue, ok := actual.(fory.Date)
+       if !ok {
+               return false
+       }
+       return expectedValue == actualValue
+}
+
+func anyTimeEqual(expected, actual any) bool {
+       expectedValue, ok := expected.(time.Time)
+       if !ok {
+               return false
+       }
+       actualValue, ok := actual.(time.Time)
+       if !ok {
+               return false
+       }
+       return expectedValue.Equal(actualValue)
+}
+
+func anyInnerEqual(expected, actual any) bool {
+       expectedValue, ok := normalizeAnyInner(expected)
+       if !ok {
+               return false
+       }
+       actualValue, ok := normalizeAnyInner(actual)
+       if !ok {
+               return false
+       }
+       return expectedValue == actualValue
+}
+
+func normalizeAnyInner(value any) (string, bool) {
+       switch typed := value.(type) {
+       case *anyexample.AnyInner:
+               if typed == nil {
+                       return "", true
+               }
+               return typed.Name, true
+       case anyexample.AnyInner:
+               return typed.Name, true
+       default:
+               return "", false
+       }
+}
+
+func anyStringSliceEqual(expected, actual any) bool {
+       expectedValue, ok := normalizeStringSlice(expected)
+       if !ok {
+               return false
+       }
+       actualValue, ok := normalizeStringSlice(actual)
+       if !ok {
+               return false
+       }
+       return reflect.DeepEqual(expectedValue, actualValue)
+}
+
+func normalizeStringSlice(value any) ([]string, bool) {
+       switch typed := value.(type) {
+       case []string:
+               return typed, true
+       case []any:
+               out := make([]string, 0, len(typed))
+               for _, item := range typed {
+                       str, ok := item.(string)
+                       if !ok {
+                               return nil, false
+                       }
+                       out = append(out, str)
+               }
+               return out, true
+       default:
+               return nil, false
+       }
+}
+
+func anyStringMapEqual(expected, actual any) bool {
+       expectedValue, ok := normalizeStringMap(expected)
+       if !ok {
+               return false
+       }
+       actualValue, ok := normalizeStringMap(actual)
+       if !ok {
+               return false
+       }
+       return reflect.DeepEqual(expectedValue, actualValue)
+}
+
+func normalizeStringMap(value any) (map[string]string, bool) {
+       switch typed := value.(type) {
+       case map[string]string:
+               return typed, true
+       case map[any]any:
+               out := make(map[string]string, len(typed))
+               for k, v := range typed {
+                       key, ok := k.(string)
+                       if !ok {
+                               return nil, false
+                       }
+                       val, ok := v.(string)
+                       if !ok {
+                               return nil, false
+                       }
+                       out[key] = val
+               }
+               return out, true
+       default:
+               return nil, false
+       }
+}
+
 func runLocalRoundTrip(t *testing.T, f *fory.Fory, book AddressBook) {
        data, err := f.Serialize(&book)
        if err != nil {
diff --git a/integration_tests/idl_tests/rust/src/lib.rs 
b/integration_tests/idl_tests/idl/any_example.fdl
similarity index 66%
copy from integration_tests/idl_tests/rust/src/lib.rs
copy to integration_tests/idl_tests/idl/any_example.fdl
index 7ad67bff6..efb982b7a 100644
--- a/integration_tests/idl_tests/rust/src/lib.rs
+++ b/integration_tests/idl_tests/idl/any_example.fdl
@@ -15,9 +15,25 @@
 // specific language governing permissions and limitations
 // under the License.
 
-pub mod addressbook;
-pub mod complex_fbs;
-pub mod monster;
-pub mod optional_types;
-pub mod graph;
-pub mod tree;
+package any_example;
+
+message AnyInner [id=300] {
+    string name = 1;
+}
+
+union AnyUnion [id=301] {
+    string text = 1;
+    bool flag = 2;
+    AnyInner inner = 3;
+}
+
+message AnyHolder [id=302] {
+    any bool_value = 1;
+    any string_value = 2;
+    any date_value = 3;
+    any timestamp_value = 4;
+    any message_value = 5;
+    any union_value = 6;
+    any list_value = 7;
+    any map_value = 8;
+}
diff --git a/integration_tests/idl_tests/idl/any_example.proto 
b/integration_tests/idl_tests/idl/any_example.proto
new file mode 100644
index 000000000..7b8c44d70
--- /dev/null
+++ b/integration_tests/idl_tests/idl/any_example.proto
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package any_example_pb;
+
+import "google/protobuf/any.proto";
+
+message AnyInner {
+  option (fory).id = 300;
+  string name = 1;
+}
+
+message AnyUnion {
+  option (fory).id = 301;
+  oneof kind {
+    string text = 1;
+    bool flag = 2;
+    AnyInner inner = 3;
+  }
+}
+
+message AnyHolder {
+  option (fory).id = 302;
+  google.protobuf.Any bool_value = 1;
+  google.protobuf.Any string_value = 2;
+  google.protobuf.Any date_value = 3;
+  google.protobuf.Any timestamp_value = 4;
+  google.protobuf.Any message_value = 5;
+  google.protobuf.Any union_value = 6;
+  google.protobuf.Any list_value = 7;
+  google.protobuf.Any map_value = 8;
+}
diff --git 
a/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
 
b/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
index 62cd1991c..d5cc3e93e 100644
--- 
a/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
+++ 
b/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
@@ -28,6 +28,10 @@ import addressbook.Person;
 import addressbook.Person.PhoneNumber;
 import addressbook.Person.PhoneType;
 import addressbook.PrimitiveTypes;
+import any_example.AnyExampleForyRegistration;
+import any_example.AnyHolder;
+import any_example.AnyInner;
+import any_example.AnyUnion;
 import complex_fbs.ComplexFbsForyRegistration;
 import complex_fbs.Container;
 import complex_fbs.Metric;
@@ -158,6 +162,19 @@ public class IdlRoundTripTest {
     }
   }
 
+  @Test
+  public void testAnyRoundTrip() {
+    Fory fory = Fory.builder().withLanguage(Language.XLANG).build();
+    AnyExampleForyRegistration.register(fory);
+
+    AnyHolder holder = buildAnyHolder();
+    byte[] bytes = fory.serialize(holder);
+    Object decoded = fory.deserialize(bytes);
+
+    Assert.assertTrue(decoded instanceof AnyHolder);
+    Assert.assertEquals(decoded, holder);
+  }
+
   @Test
   public void testTreeRoundTrip() throws Exception {
     Fory fory = 
Fory.builder().withLanguage(Language.XLANG).withRefTracking(true).build();
@@ -487,6 +504,22 @@ public class IdlRoundTripTest {
     return holder;
   }
 
+  private AnyHolder buildAnyHolder() {
+    AnyInner inner = new AnyInner();
+    inner.setName("inner");
+
+    AnyHolder holder = new AnyHolder();
+    holder.setBoolValue(Boolean.TRUE);
+    holder.setStringValue("hello");
+    holder.setDateValue(LocalDate.of(2024, 1, 2));
+    holder.setTimestampValue(Instant.ofEpochSecond(1704164645L));
+    holder.setMessageValue(inner);
+    holder.setUnionValue(AnyUnion.ofText("union"));
+    holder.setListValue(Arrays.asList("alpha", "beta"));
+    holder.setMapValue(new HashMap<>(Map.of("k1", "v1", "k2", "v2")));
+    return holder;
+  }
+
   private TreeNode buildTree() {
     TreeNode childA = new TreeNode();
     childA.setId("child-a");
diff --git a/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py 
b/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
index 96e76dfbc..e71d8a7e6 100644
--- a/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
+++ b/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
@@ -22,6 +22,7 @@ import os
 from pathlib import Path
 
 import addressbook
+import any_example
 import complex_fbs
 import monster
 import optional_types
@@ -139,6 +140,28 @@ def build_optional_holder() -> 
"optional_types.OptionalHolder":
     return optional_types.OptionalHolder(all_types=all_types, 
choice=union_value)
 
 
+def build_any_holder() -> "any_example.AnyHolder":
+    inner = any_example.AnyInner(name="inner")
+    union_value = any_example.AnyUnion.text("union")
+    return any_example.AnyHolder(
+        bool_value=True,
+        string_value="hello",
+        date_value=datetime.date(2024, 1, 2),
+        timestamp_value=datetime.datetime.fromtimestamp(1704164645),
+        message_value=inner,
+        union_value=union_value,
+        list_value=["alpha", "beta"],
+        map_value={"k1": "v1", "k2": "v2"},
+    )
+
+
+def local_roundtrip_any(fory: pyfory.Fory, holder: "any_example.AnyHolder") -> 
None:
+    data = fory.serialize(holder)
+    decoded = fory.deserialize(data)
+    assert isinstance(decoded, any_example.AnyHolder)
+    assert decoded == holder
+
+
 def build_monster() -> "monster.Monster":
     pos = monster.Vec3(x=1.0, y=2.0, z=3.0)
     return monster.Monster(
@@ -435,6 +458,7 @@ def main() -> int:
     monster.register_monster_types(fory)
     complex_fbs.register_complex_fbs_types(fory)
     optional_types.register_optional_types_types(fory)
+    any_example.register_any_example_types(fory)
 
     book = build_address_book()
     local_roundtrip(fory, book)
@@ -456,6 +480,9 @@ def main() -> int:
     local_roundtrip_optional_types(fory, holder)
     file_roundtrip_optional_types(fory, holder)
 
+    any_holder = build_any_holder()
+    local_roundtrip_any(fory, any_holder)
+
     ref_fory = pyfory.Fory(xlang=True, ref=True)
     tree.register_tree_types(ref_fory)
     graph.register_graph_types(ref_fory)
diff --git a/integration_tests/idl_tests/rust/src/lib.rs 
b/integration_tests/idl_tests/rust/src/lib.rs
index 7ad67bff6..9012b5fbb 100644
--- a/integration_tests/idl_tests/rust/src/lib.rs
+++ b/integration_tests/idl_tests/rust/src/lib.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 pub mod addressbook;
+pub mod any_example;
 pub mod complex_fbs;
 pub mod monster;
 pub mod optional_types;
diff --git a/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs 
b/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
index bf6b984e1..00a679d02 100644
--- a/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
+++ b/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
@@ -29,6 +29,7 @@ use idl_tests::addressbook::{
 use idl_tests::complex_fbs::{self, Container, Note, Payload, ScalarPack, 
Status};
 use idl_tests::monster::{self, Color, Monster, Vec3};
 use idl_tests::optional_types::{self, AllOptionalTypes, OptionalHolder, 
OptionalUnion};
+use idl_tests::any_example::{self, AnyHolder, AnyInner, AnyUnion};
 use idl_tests::{graph, tree};
 
 fn build_address_book() -> AddressBook {
@@ -185,6 +186,85 @@ fn build_optional_holder() -> OptionalHolder {
     }
 }
 
+fn build_any_holder() -> AnyHolder {
+    AnyHolder {
+        bool_value: Box::new(true),
+        string_value: Box::new("hello".to_string()),
+        date_value: Box::new(NaiveDate::from_ymd_opt(2024, 1, 2).unwrap()),
+        timestamp_value: Box::new(
+            NaiveDate::from_ymd_opt(2024, 1, 2)
+                .unwrap()
+                .and_hms_opt(3, 4, 5)
+                .expect("timestamp"),
+        ),
+        message_value: Box::new(AnyInner {
+            name: "inner".to_string(),
+        }),
+        union_value: Box::new(AnyUnion::Text("union".to_string())),
+        list_value: Box::new("list-placeholder".to_string()),
+        map_value: Box::new("map-placeholder".to_string()),
+    }
+}
+
+fn build_any_holder_with_collections() -> AnyHolder {
+    AnyHolder {
+        bool_value: Box::new(true),
+        string_value: Box::new("hello".to_string()),
+        date_value: Box::new(NaiveDate::from_ymd_opt(2024, 1, 2).unwrap()),
+        timestamp_value: Box::new(
+            NaiveDate::from_ymd_opt(2024, 1, 2)
+                .unwrap()
+                .and_hms_opt(3, 4, 5)
+                .expect("timestamp"),
+        ),
+        message_value: Box::new(AnyInner {
+            name: "inner".to_string(),
+        }),
+        union_value: Box::new(AnyUnion::Text("union".to_string())),
+        list_value: Box::new(vec!["alpha".to_string(), "beta".to_string()]),
+        map_value: Box::new(HashMap::from([
+            ("k1".to_string(), "v1".to_string()),
+            ("k2".to_string(), "v2".to_string()),
+        ])),
+    }
+}
+
+fn assert_any_holder(holder: &AnyHolder) {
+    let bool_value = holder.bool_value.downcast_ref::<bool>().expect("bool 
any");
+    assert_eq!(*bool_value, true);
+    let string_value = holder
+        .string_value
+        .downcast_ref::<String>()
+        .expect("string any");
+    assert_eq!(string_value, "hello");
+    let date_value = holder
+        .date_value
+        .downcast_ref::<NaiveDate>()
+        .expect("date any");
+    assert_eq!(*date_value, NaiveDate::from_ymd_opt(2024, 1, 2).unwrap());
+    let timestamp_value = holder
+        .timestamp_value
+        .downcast_ref::<chrono::NaiveDateTime>()
+        .expect("timestamp any");
+    assert_eq!(
+        *timestamp_value,
+        NaiveDate::from_ymd_opt(2024, 1, 2)
+            .unwrap()
+            .and_hms_opt(3, 4, 5)
+            .expect("timestamp")
+    );
+    let message_value = holder
+        .message_value
+        .downcast_ref::<AnyInner>()
+        .expect("message any");
+    assert_eq!(message_value.name, "inner");
+    let union_value = holder
+        .union_value
+        .downcast_ref::<AnyUnion>()
+        .expect("union any");
+    assert_eq!(*union_value, AnyUnion::Text("union".to_string()));
+}
+
 fn build_tree() -> tree::TreeNode {
     let mut child_a = Arc::new(tree::TreeNode {
         id: "child-a".to_string(),
@@ -302,6 +382,7 @@ fn test_address_book_roundtrip() {
     monster::register_types(&mut fory).expect("register monster types");
     complex_fbs::register_types(&mut fory).expect("register flatbuffers 
types");
     optional_types::register_types(&mut fory).expect("register optional 
types");
+    any_example::register_types(&mut fory).expect("register any example 
types");
 
     let book = build_address_book();
     let bytes = fory.serialize(&book).expect("serialize");
@@ -389,6 +470,18 @@ fn test_address_book_roundtrip() {
         fs::write(data_file, encoded).expect("write data file");
     }
 
+    let any_holder = build_any_holder();
+    let bytes = fory.serialize(&any_holder).expect("serialize any");
+    let roundtrip: AnyHolder = fory.deserialize(&bytes).expect("deserialize 
any");
+    assert_any_holder(&roundtrip);
+
+    let any_holder_collections = build_any_holder_with_collections();
+    let bytes = fory
+        .serialize(&any_holder_collections)
+        .expect("serialize any collections");
+    let result: Result<AnyHolder, _> = fory.deserialize(&bytes);
+    assert!(result.is_err());
+
     let mut ref_fory = Fory::default().xlang(true).track_ref(true);
     tree::register_types(&mut ref_fory).expect("register tree types");
     graph::register_types(&mut ref_fory).expect("register graph types");
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index 2ae96d5ad..5eee7977d 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -427,6 +427,8 @@ public abstract class TypeResolver {
       case Types.ENUM:
       case Types.STRUCT:
       case Types.EXT:
+      case Types.UNION:
+      case Types.TYPED_UNION:
         classInfo = requireUserTypeInfoByTypeId(header);
         break;
       case Types.LIST:
@@ -475,6 +477,8 @@ public abstract class TypeResolver {
       case Types.ENUM:
       case Types.STRUCT:
       case Types.EXT:
+      case Types.UNION:
+      case Types.TYPED_UNION:
         classInfo = requireUserTypeInfoByTypeId(header);
         break;
       case Types.LIST:
@@ -529,6 +533,8 @@ public abstract class TypeResolver {
       case Types.ENUM:
       case Types.STRUCT:
       case Types.EXT:
+      case Types.UNION:
+      case Types.TYPED_UNION:
         classInfo = requireUserTypeInfoByTypeId(header);
         break;
       case Types.LIST:
@@ -583,6 +589,8 @@ public abstract class TypeResolver {
       case Types.ENUM:
       case Types.STRUCT:
       case Types.EXT:
+      case Types.UNION:
+      case Types.TYPED_UNION:
         classInfo = requireUserTypeInfoByTypeId(header);
         break;
       case Types.LIST:


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to