You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/04/28 17:05:01 UTC

(fory) branch main updated: feat: add nested container codec for rust (#3630)

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 79aa38a28 feat: add nested container codec for rust (#3630)
79aa38a28 is described below

commit 79aa38a285606b32911aca48d628226925e24092
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Wed Apr 29 01:04:56 2026 +0800

    feat: add nested container codec for rust (#3630)
    
    ## Why?
    
    Rust generated serializers need field-local codec selection so nested
    container element/key/value metadata, nullability, reference tracking,
    and integer encodings can be represented consistently in compatible and
    xlang serialization. The Rust derive API also now distinguishes structs,
    pure enums, and union-style enums instead of routing every data shape
    through one `ForyObject` derive.
    
    ## What does this PR do?
    
    - Adds Rust field codecs for serializer-backed fields, options,
    lists/sets, maps, nested container entries, integer encodings, `Any`,
    and trait object fields.
    - Reworks `fory-derive` field binding, read, write, and metadata
    generation to dispatch through codecs when field annotations or nested
    metadata require it, while preserving declared generic metadata for
    unannotated container fields.
    - Splits the Rust derive macros into `ForyStruct`, `ForyEnum`, and
    `ForyUnion`, with validation for applying each macro to the matching
    Rust data shape.
    - Updates Rust IDL/codegen output to emit nested `list(...)` and
    `map(...)` field attributes and the new derive macros.
    - Updates Rust docs, README examples, benchmark fixtures, and tests from
    `ForyObject` to the new derive names.
    - Adds Rust coverage for nested codec annotations, compatible-mode
    integer encoding mismatches, collection generic metadata,
    trait-object/`Any` containers, and serialized benchmark payload sizes.
    - Adds a dedicated C++ IDL CI job setup and keeps the C++ xlang job
    naming consistent.
    
    ## Related issues
    
    #1017
    #3625
    
    ## AI Contribution Checklist
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    ## Does this PR introduce any user-facing change?
    
    - [x] Does this PR introduce any public API change?
    - [x] Does this PR introduce any binary protocol compatibility change?
    
    Rust users should derive `ForyStruct`, `ForyEnum`, or `ForyUnion`
    instead of the previous `ForyObject` macro. Rust generated serializers
    can now emit nested list/map field metadata and field-level codec
    behavior for annotated containers.
    
    ## Benchmark
    
    No benchmark results are included in the PR diff. The Rust benchmark
    test fixture now asserts serialized size baselines for the covered
    payloads.
---
 .github/workflows/ci.yml                           |   29 +-
 AGENTS.md                                          |    3 +-
 README.md                                          |    8 +-
 benchmarks/rust/benches/threaded_bench.rs          |    4 +-
 benchmarks/rust/src/data.rs                        |   22 +-
 benchmarks/rust/src/lib.rs                         |   23 +
 benchmarks/rust/src/serializers/fory.rs            |    2 +-
 compiler/README.md                                 |    4 +-
 compiler/fory_compiler/generators/rust.py          |   70 +-
 docs/compiler/generated-code.md                    |   12 +-
 docs/guide/rust/basic-serialization.md             |   10 +-
 docs/guide/rust/cross-language.md                  |    4 +-
 docs/guide/rust/custom-serializers.md              |    6 +-
 docs/guide/rust/field-configuration.md             |   97 +-
 docs/guide/rust/index.md                           |   10 +-
 docs/guide/rust/polymorphism.md                    |   10 +-
 docs/guide/rust/references.md                      |    8 +-
 docs/guide/rust/row-format.md                      |    4 +-
 docs/guide/rust/schema-evolution.md                |   20 +-
 docs/guide/rust/troubleshooting.md                 |    4 +-
 docs/guide/rust/type-registration.md               |    4 +-
 docs/guide/xlang/field-nullability.md              |    4 +-
 docs/guide/xlang/field-reference-tracking.md       |    4 +-
 docs/guide/xlang/getting-started.md                |    4 +-
 docs/guide/xlang/serialization.md                  |    6 +-
 rust/README.md                                     |   36 +-
 .../src/object/mod.rs => fory-core/build.rs}       |   17 +-
 rust/fory-core/src/context.rs                      |   42 +
 rust/fory-core/src/fory.rs                         |   40 +-
 rust/fory-core/src/lib.rs                          |   12 +-
 rust/fory-core/src/meta/type_meta.rs               |  155 +-
 rust/fory-core/src/resolver/meta_resolver.rs       |   38 +-
 rust/fory-core/src/serializer/arc.rs               |    5 +-
 rust/fory-core/src/serializer/box_.rs              |    5 +-
 rust/fory-core/src/serializer/codec.rs             | 2547 ++++++++++++++++++++
 rust/fory-core/src/serializer/core.rs              |   22 +-
 rust/fory-core/src/serializer/heap.rs              |    8 +
 rust/fory-core/src/serializer/mod.rs               |    2 +
 rust/fory-core/src/serializer/mutex.rs             |    5 +-
 rust/fory-core/src/serializer/rc.rs                |    5 +-
 rust/fory-core/src/serializer/refcell.rs           |    5 +-
 rust/fory-core/src/serializer/skip.rs              |  152 +-
 rust/fory-core/src/serializer/struct_.rs           |    5 +
 rust/fory-core/src/serializer/trait_object.rs      |  366 ++-
 rust/fory-core/src/serializer/weak.rs              |   10 +-
 rust/fory-core/src/util/mod.rs                     |    6 +-
 rust/fory-derive/src/lib.rs                        |  125 +-
 rust/fory-derive/src/object/derive_enum.rs         |  328 ++-
 rust/fory-derive/src/object/field_codec.rs         | 1175 +++++++++
 rust/fory-derive/src/object/field_meta.rs          |  597 +++--
 rust/fory-derive/src/object/misc.rs                |  198 +-
 rust/fory-derive/src/object/mod.rs                 |    1 +
 rust/fory-derive/src/object/read.rs                |  921 +------
 rust/fory-derive/src/object/serializer.rs          |   50 +-
 rust/fory-derive/src/object/util.rs                |  899 +------
 rust/fory-derive/src/object/write.rs               |  386 +--
 rust/fory-derive/src/util.rs                       |   69 -
 rust/fory/src/lib.rs                               |   86 +-
 rust/tests/tests/compatible/test_container.rs      |    4 +-
 rust/tests/tests/compatible/test_struct.rs         |   64 +-
 rust/tests/tests/compatible/test_struct_enum.rs    |   58 +-
 rust/tests/tests/mod.rs                            |    1 +
 rust/tests/tests/test_any.rs                       |   57 +-
 rust/tests/tests/test_array.rs                     |   14 +-
 rust/tests/tests/test_associated_types.rs          |    4 +-
 rust/tests/tests/test_box.rs                       |    6 +-
 rust/tests/tests/test_collection.rs                |    6 +-
 rust/tests/tests/test_complex_struct.rs            |   16 +-
 rust/tests/tests/test_cross_language.rs            |   98 +-
 rust/tests/tests/test_debug.rs                     |    2 +-
 rust/tests/tests/test_enum.rs                      |   49 +-
 rust/tests/tests/test_enum_compatible.rs           |   42 +-
 rust/tests/tests/test_ext.rs                       |    4 +-
 rust/tests/tests/test_field_meta.rs                |  389 ++-
 rust/tests/tests/test_fory.rs                      |   18 +-
 rust/tests/tests/test_generate_default.rs          |   22 +-
 rust/tests/tests/test_lifecycle_guard.rs           |    6 +-
 rust/tests/tests/test_list.rs                      |    4 +-
 rust/tests/tests/test_map.rs                       |    4 +-
 rust/tests/tests/test_marker.rs                    |   10 +-
 rust/tests/tests/test_max_dyn_depth.rs             |    4 +-
 rust/tests/tests/test_meta.rs                      |    8 +-
 rust/tests/tests/test_multi_thread.rs              |    6 +-
 rust/tests/tests/test_one_struct.rs                |    6 +-
 rust/tests/tests/test_rc_arc.rs                    |    4 +-
 rust/tests/tests/test_rc_arc_trait_object.rs       |   60 +-
 rust/tests/tests/test_refcell.rs                   |    6 +-
 rust/tests/tests/test_simple_struct.rs             |   34 +-
 rust/tests/tests/test_skip_fields.rs               |   32 +-
 rust/tests/tests/test_trait_object.rs              |   14 +-
 rust/tests/tests/test_tuple.rs                     |    8 +-
 rust/tests/tests/test_tuple_compatible.rs          |   34 +-
 rust/tests/tests/test_tuple_struct.rs              |   58 +-
 rust/tests/tests/test_unsigned.rs                  |   24 +-
 rust/tests/tests/test_weak.rs                      |   10 +-
 95 files changed, 6390 insertions(+), 3486 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 243cffa28..5018d44ce 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -702,7 +702,7 @@ jobs:
           if-no-files-found: ignore
 
   cpp_xlang:
-    name: C++ Xlang Test
+    name: CPP Xlang Test
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -737,6 +737,33 @@ jobs:
           mvn -T16 --no-transfer-progress clean install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
           cd fory-core
           mvn -T16 --no-transfer-progress test -Dtest=org.apache.fory.xlang.CPPXlangTest
+
+  cpp_idl:
+    name: C++ IDL Tests
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v5
+      - name: Set up Python 3.11
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+          cache: 'pip'
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: 21
+          distribution: "temurin"
+      - name: Cache Maven local repository
+        uses: actions/cache@v4
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Install Java artifacts for IDL tests
+        run: |
+          cd java
+          mvn -T16 --no-transfer-progress clean install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
       - name: Run C++ IDL Tests
         run: ./integration_tests/idl_tests/run_cpp_tests.sh
 
diff --git a/AGENTS.md b/AGENTS.md
index ca1b7a721..bb92c1387 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,6 +36,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
 - Verification is required. Match validation to the real ownership path: compile, tests, xlang, native-image, non-VM compile, benchmarks, and remote CI as applicable; reasoning alone is never enough.
 - Finish the whole surface. A feature or behavior change is incomplete until code, tests, docs, exports, examples, and build wiring agree, unless the user explicitly defers part of that surface.
 - Keep task boundaries strict. Review tasks do not edit code, analysis-only tasks do not silently turn into implementation, and active-branch fixes must land in the active branch/workspace.
+- For non-trivial multi-step tasks, write the plan and progress into the canonical durable task file (use a matched skill/workflow file if it provides one, otherwise use a file under `tasks/`) and read that file after compaction before continuing.
 
 ## Repo-Wide Hard Rules
 
@@ -137,7 +138,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
 
 ## Commit And PR Expectations
 
-- After each finished task, create a git commit automatically for the task's tracked code and documentation changes, excluding `tasks/task-*.md`, `tasks/lessons.md`, and unrelated user changes.
+- After each finished task, create a git commit automatically for the task's tracked code and documentation changes, excluding `tasks/task-*.md`, `tasks/*-plan.md`, `tasks/*-state.md`, `tasks/lessons.md`, and unrelated user changes.
 - PR titles must follow Conventional Commits; `.github/workflows/pr-lint.yml` enforces this.
 - Performance changes should use the `perf` type and include benchmark data.
 - See `.agents/ci-and-pr.md` for GitHub CLI triage commands and commit message examples.
diff --git a/README.md b/README.md
index 2d2bd8fc5..449694cd3 100644
--- a/README.md
+++ b/README.md
@@ -317,9 +317,9 @@ For detailed Python usage including type hints, compatibility modes, and advance
 Rust native mode provides compile-time code generation via derive macros for high-performance serialization without runtime overhead.
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct Person {
     name: String,
     age: i32,
@@ -564,9 +564,9 @@ public class XlangExample {
 **Rust**
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct Person {
     name: String,
     age: i32,
diff --git a/benchmarks/rust/benches/threaded_bench.rs b/benchmarks/rust/benches/threaded_bench.rs
index 7895ef7e0..82469d614 100644
--- a/benchmarks/rust/benches/threaded_bench.rs
+++ b/benchmarks/rust/benches/threaded_bench.rs
@@ -17,12 +17,12 @@
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use fory::Fory;
-use fory_derive::ForyObject;
+use fory_derive::ForyStruct;
 use std::hint::black_box;
 use std::sync::Arc;
 use std::thread;
 
-#[derive(Debug, ForyObject)]
+#[derive(Debug, ForyStruct)]
 pub struct UserSessionMetrics {
     #[fory(id = 0)]
     pub request_count: u64,
diff --git a/benchmarks/rust/src/data.rs b/benchmarks/rust/src/data.rs
index 95178ce19..5ab46c181 100644
--- a/benchmarks/rust/src/data.rs
+++ b/benchmarks/rust/src/data.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 use fory::{Error, Fory};
-use fory_derive::ForyObject;
+use fory_derive::{ForyEnum, ForyStruct};
 
 const LIST_SIZE: usize = 5;
 
@@ -69,7 +69,7 @@ pub trait BenchmarkCase: Clone + PartialEq + Sized + 'static {
     fn create() -> Self;
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct NumericStruct {
     #[fory(id = 1)]
     pub f1: i32,
@@ -89,7 +89,7 @@ pub struct NumericStruct {
     pub f8: i32,
 }
 
-#[derive(Debug, Clone, PartialEq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, ForyStruct)]
 pub struct Sample {
     #[fory(id = 1)]
     pub int_value: i32,
@@ -137,7 +137,7 @@ pub struct Sample {
     pub string: String,
 }
 
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyEnum)]
 #[repr(i32)]
 pub enum Player {
     #[default]
@@ -145,7 +145,7 @@ pub enum Player {
     Flash = 1,
 }
 
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyEnum)]
 #[repr(i32)]
 pub enum Size {
     #[default]
@@ -153,7 +153,7 @@ pub enum Size {
     Large = 1,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct Media {
     #[fory(id = 1)]
     pub uri: String,
@@ -181,7 +181,7 @@ pub struct Media {
     pub copyright: String,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct Image {
     #[fory(id = 1)]
     pub uri: String,
@@ -195,7 +195,7 @@ pub struct Image {
     pub size: Size,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct MediaContent {
     #[fory(id = 1)]
     pub media: Media,
@@ -203,19 +203,19 @@ pub struct MediaContent {
     pub images: Vec<Image>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct StructList {
     #[fory(id = 1)]
     pub struct_list: Vec<NumericStruct>,
 }
 
-#[derive(Debug, Clone, PartialEq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, ForyStruct)]
 pub struct SampleList {
     #[fory(id = 1)]
     pub sample_list: Vec<Sample>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
 pub struct MediaContentList {
     #[fory(id = 1)]
     pub media_content_list: Vec<MediaContent>,
diff --git a/benchmarks/rust/src/lib.rs b/benchmarks/rust/src/lib.rs
index 65575f63a..d4fa25e9e 100644
--- a/benchmarks/rust/src/lib.rs
+++ b/benchmarks/rust/src/lib.rs
@@ -110,6 +110,19 @@ mod tests {
         assert_eq!(value, decoded);
     }
 
+    fn assert_serialized_size<T>(expected_fory: usize, expected_protobuf: usize)
+    where
+        T: BenchmarkCase,
+        ForySerializer: BenchmarkSerializer<T>,
+        ProtobufSerializer: BenchmarkSerializer<T>,
+    {
+        let value = T::create();
+        let fory_bytes = ForySerializer::new().serialize(&value).unwrap();
+        let protobuf_bytes = ProtobufSerializer::new().serialize(&value).unwrap();
+        assert_eq!(fory_bytes.len(), expected_fory);
+        assert_eq!(protobuf_bytes.len(), expected_protobuf);
+    }
+
     #[test]
     fn benchmark_cases_round_trip() {
         assert_round_trip::<NumericStruct>();
@@ -119,4 +132,14 @@ mod tests {
         assert_round_trip::<SampleList>();
         assert_round_trip::<MediaContentList>();
     }
+
+    #[test]
+    fn benchmark_serialized_sizes_match_baseline() {
+        assert_serialized_size::<NumericStruct>(58, 61);
+        assert_serialized_size::<Sample>(446, 375);
+        assert_serialized_size::<MediaContent>(365, 301);
+        assert_serialized_size::<StructList>(184, 315);
+        assert_serialized_size::<SampleList>(1980, 1890);
+        assert_serialized_size::<MediaContentList>(1535, 1520);
+    }
 }
diff --git a/benchmarks/rust/src/serializers/fory.rs b/benchmarks/rust/src/serializers/fory.rs
index fb4f6e6b6..0a85a5e17 100644
--- a/benchmarks/rust/src/serializers/fory.rs
+++ b/benchmarks/rust/src/serializers/fory.rs
@@ -26,7 +26,7 @@ pub struct ForySerializer {
 
 impl ForySerializer {
     pub fn new() -> Self {
-        let mut fory = Fory::default().xlang(true).compatible(true);
+        let mut fory = Fory::builder().xlang(true).compatible(true).build();
         register_fory_types(&mut fory).expect("register benchmark types");
 
         Self { fory }
diff --git a/compiler/README.md b/compiler/README.md
index bdf015fec..aae1e7975 100644
--- a/compiler/README.md
+++ b/compiler/README.md
@@ -364,12 +364,12 @@ type Cat struct {
 
 Generates structs with:
 
-- `#[derive(ForyObject)]` macro
+- `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]` macros
 - `#[fory(...)]` field attributes
 - a registration helper for namespace-based registration
 
 ```rust
-#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+#[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
 pub struct Cat {
     pub friend: Arc<Dog>,
     #[fory(nullable = true)]
diff --git a/compiler/fory_compiler/generators/rust.py b/compiler/fory_compiler/generators/rust.py
index 47bb087f4..7395b3a07 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -38,7 +38,7 @@ from fory_compiler.ir.types import PrimitiveKind
 
 
 class RustGenerator(BaseGenerator):
-    """Generates Rust structs with ForyObject derive macro."""
+    """Generates Rust types with Fory derive macros."""
 
     language_name = "rust"
     file_extension = ".rs"
@@ -200,7 +200,7 @@ class RustGenerator(BaseGenerator):
         uses: Set[str] = set()
 
         # Collect uses (including from nested types)
-        uses.add("use fory::{Fory, ForyObject}")
+        uses.add("use fory::{Fory, ForyEnum, ForyStruct, ForyUnion}")
         uses.add("use std::sync::OnceLock")
 
         for message in self.schema.messages:
@@ -337,7 +337,7 @@ class RustGenerator(BaseGenerator):
         type_name = enum.name
 
         # Derive macros
-        lines.append("#[derive(ForyObject, Debug, Clone, PartialEq, Default)]")
+        lines.append("#[derive(ForyEnum, Debug, Clone, PartialEq, Default)]")
         lines.append("#[repr(i32)]")
 
         lines.append(f"pub enum {type_name} {{")
@@ -369,7 +369,7 @@ class RustGenerator(BaseGenerator):
         comment = self.format_type_id_comment(union, "//")
         if comment:
             lines.append(comment)
-        derives = ["ForyObject", "Debug"]
+        derives = ["ForyUnion", "Debug"]
         if not has_any:
             derives.extend(["Clone", "PartialEq"])
         lines.append(f"#[derive({', '.join(derives)})]")
@@ -421,7 +421,7 @@ class RustGenerator(BaseGenerator):
         if comment:
             lines.append(comment)
         needs_safe_debug = self.message_needs_safe_debug(message)
-        derives = ["ForyObject"]
+        derives = ["ForyStruct"]
         if not needs_safe_debug:
             derives.append("Debug")
         if not self.message_has_any(message):
@@ -600,14 +600,13 @@ class RustGenerator(BaseGenerator):
             attrs.append("nullable = true")
         if field.ref:
             attrs.append("ref = true")
-        encoding = self.get_encoding_attr(field.field_type)
-        if encoding:
-            attrs.append(encoding)
-        array_attr = self.get_array_attr(field)
-        if array_attr:
-            attrs.append(array_attr)
-        if self.is_union_type(field.field_type, parent_stack):
-            attrs.append('type_id = "union"')
+        nested_attr = self.get_nested_field_attr(field.field_type)
+        if nested_attr:
+            attrs.append(nested_attr)
+        else:
+            encoding = self.get_encoding_attr(field.field_type)
+            if encoding:
+                attrs.append(encoding)
         if attrs:
             lines.append(f"#[fory({', '.join(attrs)})]")
 
@@ -647,26 +646,43 @@ class RustGenerator(BaseGenerator):
             PrimitiveKind.UINT32,
             PrimitiveKind.UINT64,
         ):
-            return 'encoding = "fixed"'
+            return "encoding = fixed"
         if kind in (PrimitiveKind.TAGGED_INT64, PrimitiveKind.TAGGED_UINT64):
-            return 'encoding = "tagged"'
+            return "encoding = tagged"
         return None
 
-    def get_array_attr(self, field: Field) -> Optional[str]:
-        """Return type_id attribute for int8/uint8 arrays."""
-        if not isinstance(field.field_type, ListType):
-            return None
-        if field.element_optional or field.element_ref:
-            return None
-        element_type = field.field_type.element_type
-        if not isinstance(element_type, PrimitiveType):
+    def get_nested_field_attr(self, field_type: FieldType) -> Optional[str]:
+        """Return nested list/map field configuration."""
+        if isinstance(field_type, ListType):
+            element_attrs = self.get_nested_value_attrs(field_type.element_type)
+            if element_attrs:
+                return f"list(element({', '.join(element_attrs)}))"
             return None
-        if element_type.kind == PrimitiveKind.INT8:
-            return 'type_id = "int8_array"'
-        if element_type.kind == PrimitiveKind.UINT8:
-            return 'type_id = "uint8_array"'
+        if isinstance(field_type, MapType):
+            key_attrs = self.get_nested_value_attrs(field_type.key_type)
+            value_attrs = self.get_nested_value_attrs(field_type.value_type)
+            parts = []
+            if key_attrs:
+                parts.append(f"key({', '.join(key_attrs)})")
+            if value_attrs or field_type.value_optional:
+                if field_type.value_optional:
+                    value_attrs = ["nullable = true", *value_attrs]
+                parts.append(f"value({', '.join(value_attrs)})")
+            if parts:
+                return f"map({', '.join(parts)})"
         return None
 
+    def get_nested_value_attrs(self, field_type: FieldType) -> List[str]:
+        attrs: List[str] = []
+        if isinstance(field_type, PrimitiveType):
+            encoding = self.get_encoding_attr(field_type)
+            if encoding:
+                attrs.append(encoding)
+        nested = self.get_nested_field_attr(field_type)
+        if nested:
+            attrs.append(nested)
+        return attrs
+
     def is_union_type(
         self, field_type: FieldType, parent_stack: Optional[List[Message]]
     ) -> bool:
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 232dfb89b..583cd79b6 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -336,7 +336,7 @@ Rust output is one module file per schema, for example:
 Unions map to Rust enums with `#[fory(id = ...)]` case attributes:
 
 ```rust
-#[derive(ForyObject, Debug, Clone, PartialEq)]
+#[derive(ForyUnion, Debug, Clone, PartialEq)]
 pub enum Animal {
     #[fory(id = 1)]
     Dog(Dog),
@@ -349,7 +349,7 @@ Nested types generate nested modules:
 
 ```rust
 pub mod person {
-    #[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+    #[derive(ForyEnum, Debug, Clone, PartialEq, Default)]
     #[repr(i32)]
     pub enum PhoneType {
         #[default]
@@ -358,7 +358,7 @@ pub mod person {
         Work = 2,
     }
 
-    #[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+    #[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
     pub struct PhoneNumber {
         #[fory(id = 1)]
         pub number: String,
@@ -368,16 +368,16 @@ pub mod person {
 }
 ```
 
-Messages derive `ForyObject` and include `to_bytes`/`from_bytes` helpers:
+Messages derive `ForyStruct` and include `to_bytes`/`from_bytes` helpers:
 
 ```rust
-#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+#[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
 pub struct Person {
     #[fory(id = 1)]
     pub name: String,
     #[fory(id = 7)]
     pub phones: Vec<person::PhoneNumber>,
-    #[fory(id = 8, type_id = "union")]
+    #[fory(id = 8)]
     pub pet: Animal,
 }
 ```
diff --git a/docs/guide/rust/basic-serialization.md b/docs/guide/rust/basic-serialization.md
index 9ac5e5d5a..6298ed426 100644
--- a/docs/guide/rust/basic-serialization.md
+++ b/docs/guide/rust/basic-serialization.md
@@ -23,7 +23,7 @@ This page covers basic object graph serialization and supported types.
 
 ## Object Graph Serialization
 
-Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyObject)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
+Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyStruct)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
 
 **Key capabilities:**
 
@@ -35,10 +35,10 @@ Apache Fory™ provides automatic serialization of complex object graphs, preser
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
 use std::collections::HashMap;
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct Person {
     name: String,
     age: i32,
@@ -47,7 +47,7 @@ struct Person {
     metadata: HashMap<String, String>,
 }
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct Address {
     street: String,
     city: String,
@@ -128,7 +128,7 @@ assert_eq!(person, decoded);
 
 | Macro                   | Description                |
 | ----------------------- | -------------------------- |
-| `#[derive(ForyObject)]` | Object graph serialization |
+| `#[derive(ForyStruct)]` | Object graph serialization |
 | `#[derive(ForyRow)]`    | Row-based serialization    |
 
 ## Serialization APIs
diff --git a/docs/guide/rust/cross-language.md b/docs/guide/rust/cross-language.md
index 245f17542..3c80e2c9e 100644
--- a/docs/guide/rust/cross-language.md
+++ b/docs/guide/rust/cross-language.md
@@ -66,9 +66,9 @@ fory.register_by_namespace::<User>("com.example", "User");
 
 ```rust
 use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Person {
     name: String,
     age: i32,
diff --git a/docs/guide/rust/custom-serializers.md b/docs/guide/rust/custom-serializers.md
index 87c552367..508e37e57 100644
--- a/docs/guide/rust/custom-serializers.md
+++ b/docs/guide/rust/custom-serializers.md
@@ -19,7 +19,7 @@ license: |
   limitations under the License.
 ---
 
-For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` trait manually.
+For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` trait manually.
 
 ## When to Use Custom Serializers
 
@@ -74,7 +74,7 @@ impl ForyDefault for CustomType {
 > **Note**: When implementing `ForyDefault` manually, ensure your type also implements `Default` if you use `Self::default()`.
 > Alternatively, you can construct a default instance directly in `fory_default()`.
 >
-> **Tip**: If your type supports `#[derive(ForyObject)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations.
+> **Tip**: If your type supports `#[derive(ForyStruct)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations.
 
 ## Registering Custom Serializers
 
@@ -149,5 +149,5 @@ let string = context.reader.read_utf8_string(len);
 ## Related Topics
 
 - [Type Registration](type-registration.md) - Registering serializers
-- [Basic Serialization](basic-serialization.md) - Using ForyObject derive
+- [Basic Serialization](basic-serialization.md) - Using ForyStruct derive
 - [Schema Evolution](schema-evolution.md) - Compatible mode
diff --git a/docs/guide/rust/field-configuration.md b/docs/guide/rust/field-configuration.md
index 0f9e1be2e..ac2a29762 100644
--- a/docs/guide/rust/field-configuration.md
+++ b/docs/guide/rust/field-configuration.md
@@ -36,9 +36,9 @@ Apache Fory™ provides the `#[fory(...)]` attribute macro to specify optional f
 The `#[fory(...)]` attribute is placed on individual struct fields:
 
 ```rust
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Person {
     #[fory(id = 0)]
     name: String,
@@ -60,7 +60,7 @@ Multiple options are separated by commas.
 Assigns a numeric ID to a field to minimize struct field meta size overhead:
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct User {
     #[fory(id = 0)]
     id: i64,
@@ -91,7 +91,7 @@ struct User {
 Excludes a field from serialization:
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct User {
     #[fory(id = 0)]
     id: i64,
@@ -113,7 +113,7 @@ Controls whether null flags are written for fields:
 ```rust
 use fory::{Fory, RcWeak};
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Record {
     // RcWeak is nullable by default, override to non-nullable
     #[fory(id = 0, nullable = false)]
@@ -143,7 +143,7 @@ Controls per-field reference tracking for shared ownership types:
 use std::rc::Rc;
 use std::sync::Arc;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Container {
     // Enable reference tracking (default for Rc/Arc)
     #[fory(id = 0, ref = true)]
@@ -174,18 +174,18 @@ struct Container {
 Controls how integer fields are encoded:
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Metrics {
     // Variable-length encoding (smaller for small values)
-    #[fory(id = 0, encoding = "varint")]
+    #[fory(id = 0, encoding = varint)]
     count: i64,
 
     // Fixed-length encoding (consistent size)
-    #[fory(id = 1, encoding = "fixed")]
+    #[fory(id = 1, encoding = fixed)]
     timestamp: i64,
 
     // Tagged encoding (includes type tag, u64 only)
-    #[fory(id = 2, encoding = "tagged")]
+    #[fory(id = 2, encoding = tagged)]
     value: u64,
 }
 ```
@@ -203,28 +203,26 @@ struct Metrics {
 - `fixed`: Best for values that use full range (e.g., timestamps, hashes)
 - `tagged`: When type information needs to be preserved (u64 only)
 
-### Compress (`compress`)
+### Nested Collection Configuration
 
-A convenience shorthand for controlling integer encoding:
+Use `list(element(...))` and `map(key(...), value(...))` when an override belongs
+to a nested element instead of the outer field:
 
 ```rust
-#[derive(ForyObject)]
+use std::collections::HashMap;
+
+#[derive(ForyStruct)]
 struct Data {
-    // compress = true -> varint encoding (default)
-    #[fory(id = 0, compress)]
-    small_value: i32,
+    #[fory(list(element(encoding = fixed)))]
+    fixed_values: Vec<i32>,
 
-    // compress = false -> fixed encoding
-    #[fory(id = 1, compress = false)]
-    fixed_value: u32,
+    #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))]
+    values_by_id: HashMap<Option<i32>, Option<u64>>,
 }
 ```
 
-**Notes**:
-
-- `compress` or `compress = true` is equivalent to `encoding = "varint"`
-- `compress = false` is equivalent to `encoding = "fixed"`
-- If both `compress` and `encoding` are specified, they must not conflict
+`compress` has been removed. Use `encoding = varint` or `encoding = fixed`
+directly.
 
 ## Type Classification
 
@@ -245,10 +243,10 @@ Fory classifies field types to determine default behavior:
 ## Complete Example
 
 ```rust
-use fory::ForyObject;
+use fory::ForyStruct;
 use std::rc::Rc;
 
-#[derive(ForyObject, Default)]
+#[derive(ForyStruct, Default)]
 struct Document {
     // Required fields with tag IDs
     #[fory(id = 0)]
@@ -270,11 +268,11 @@ struct Document {
     related: Option<Rc<Document>>,
 
     // Counter with varint encoding (small values)
-    #[fory(id = 5, encoding = "varint")]
+    #[fory(id = 5, encoding = varint)]
     view_count: u64,
 
     // Timestamp with fixed encoding (full range values)
-    #[fory(id = 6, encoding = "fixed")]
+    #[fory(id = 6, encoding = fixed)]
     created_at: i64,
 
     // Skip sensitive field
@@ -307,7 +305,7 @@ Invalid configurations are caught at compile time:
 
 ```rust
 // Error: duplicate field IDs
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Bad {
     #[fory(id = 0)]
     field1: String,
@@ -317,16 +315,16 @@ struct Bad {
 }
 
 // Error: invalid id value
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Bad2 {
     #[fory(id = -2)]  // Compile error: id must be >= -1
     field: String,
 }
 
-// Error: conflicting encoding attributes
-#[derive(ForyObject)]
+// Error: removed shorthand
+#[derive(ForyStruct)]
 struct Bad3 {
-    #[fory(compress = true, encoding = "fixed")]  // Compile error: conflict
+    #[fory(compress = false)]  // Compile error: use encoding = fixed
     field: i32,
 }
 ```
@@ -336,18 +334,18 @@ struct Bad3 {
 When serializing data to be read by other languages (Java, C++, Go, Python), use field configuration to match encoding expectations:
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct CrossLangData {
     // Matches Java Integer with varint
-    #[fory(id = 0, encoding = "varint")]
+    #[fory(id = 0, encoding = varint)]
     int_var: i32,
 
     // Matches Java Integer with fixed
-    #[fory(id = 1, encoding = "fixed")]
+    #[fory(id = 1, encoding = fixed)]
     int_fixed: i32,
 
     // Matches Java Long with tagged encoding
-    #[fory(id = 2, encoding = "tagged")]
+    #[fory(id = 2, encoding = tagged)]
     long_tagged: u64,
 
     // Nullable pointer matches Java nullable reference
@@ -362,7 +360,7 @@ Compatible mode supports schema evolution. It is recommended to configure field
 
 ```rust
 // Version 1
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct DataV1 {
     #[fory(id = 0)]
     id: i64,
@@ -372,7 +370,7 @@ struct DataV1 {
 }
 
 // Version 2: Added new field
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct DataV2 {
     #[fory(id = 0)]
     id: i64,
@@ -390,7 +388,7 @@ Data serialized with V1 can be deserialized with V2 (new field will be `None`).
 Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead):
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Data {
     id: i64,
     name: String,
@@ -411,7 +409,7 @@ You **need to configure fields** when:
 
 ```rust
 // Xlang mode: explicit configuration required
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct User {
     #[fory(id = 0)]
     name: String,                    // Non-nullable by default
@@ -444,14 +442,15 @@ struct User {
 
 ## Options Reference
 
-| Option     | Syntax                             | Description                          | Valid For                  |
-| ---------- | ---------------------------------- | ------------------------------------ | -------------------------- |
-| `id`       | `id = N`                           | Field tag ID to reduce metadata size | All fields                 |
-| `skip`     | `skip`                             | Exclude field from serialization     | All fields                 |
-| `nullable` | `nullable` or `nullable = bool`    | Control null flag writing            | All fields                 |
-| `ref`      | `ref` or `ref = bool`              | Control reference tracking           | `Rc`, `Arc`, weak types    |
-| `encoding` | `encoding = "varint/fixed/tagged"` | Integer encoding method              | `i32`, `u32`, `i64`, `u64` |
-| `compress` | `compress` or `compress = bool`    | Shorthand for varint/fixed           | `i32`, `u32`               |
+| Option     | Syntax                           | Description                          | Valid For                  |
+| ---------- | -------------------------------- | ------------------------------------ | -------------------------- |
+| `id`       | `id = N`                         | Field tag ID to reduce metadata size | All fields                 |
+| `skip`     | `skip`                           | Exclude field from serialization     | All fields                 |
+| `nullable` | `nullable` or `nullable = bool`  | Control null flag writing            | All fields                 |
+| `ref`      | `ref` or `ref = bool`            | Control reference tracking           | `Rc`, `Arc`, weak types    |
+| `encoding` | `encoding = varint/fixed/tagged` | Integer encoding method              | `i32`, `u32`, `i64`, `u64` |
+| `list`     | `list(element(...))`             | Element field configuration          | `Vec<T>`                   |
+| `map`      | `map(key(...), value(...))`      | Key/value field configuration        | `HashMap<K, V>`            |
 
 ## Related Topics
 
diff --git a/docs/guide/rust/index.md b/docs/guide/rust/index.md
index d52a45d52..3b32eac6d 100644
--- a/docs/guide/rust/index.md
+++ b/docs/guide/rust/index.md
@@ -54,9 +54,9 @@ fory = "0.13"
 
 ```rust
 use fory::{Fory, Error, Reader};
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct User {
     name: String,
     age: i32,
@@ -96,11 +96,11 @@ Apache Fory™ Rust is fully thread-safe: `Fory` implements both `Send` and `Syn
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
 use std::sync::Arc;
 use std::thread;
 
-#[derive(ForyObject, Clone, Copy, Debug, PartialEq)]
+#[derive(ForyStruct, Clone, Copy, Debug, PartialEq)]
 struct Item {
     value: i32,
 }
@@ -152,7 +152,7 @@ fory-core/             # Core serialization engine
 
 fory-derive/           # Procedural macros
 ├── src/
-│   ├── object/       # ForyObject macro
+│   ├── object/       # ForyStruct macro
 │   └── fory_row.rs  # ForyRow macro
 ```
 
diff --git a/docs/guide/rust/polymorphism.md b/docs/guide/rust/polymorphism.md
index 2afff965d..f3b7bbbb1 100644
--- a/docs/guide/rust/polymorphism.md
+++ b/docs/guide/rust/polymorphism.md
@@ -33,14 +33,14 @@ Apache Fory™ supports polymorphic serialization through trait objects, enablin
 ```rust
 use fory::{Fory, register_trait_type};
 use fory::Serializer;
-use fory::ForyObject;
+use fory::ForyStruct;
 
 trait Animal: Serializer {
     fn speak(&self) -> String;
     fn name(&self) -> &str;
 }
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Dog { name: String, breed: String }
 
 impl Animal for Dog {
@@ -48,7 +48,7 @@ impl Animal for Dog {
     fn name(&self) -> &str { &self.name }
 }
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Cat { name: String, color: String }
 
 impl Animal for Cat {
@@ -59,7 +59,7 @@ impl Animal for Cat {
 // Register trait implementations
 register_trait_type!(Animal, Dog, Cat);
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Zoo {
     star_animal: Box<dyn Animal>,
 }
@@ -146,7 +146,7 @@ use std::sync::Arc;
 use std::rc::Rc;
 use std::collections::HashMap;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct AnimalShelter {
     animals_rc: Vec<Rc<dyn Animal>>,
     animals_arc: Vec<Arc<dyn Animal>>,
diff --git a/docs/guide/rust/references.md b/docs/guide/rust/references.md
index e2dcb49da..f18d11cd1 100644
--- a/docs/guide/rust/references.md
+++ b/docs/guide/rust/references.md
@@ -90,12 +90,12 @@ To serialize circular references like parent-child relationships or doubly-linke
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
 use fory::RcWeak;
 use std::rc::Rc;
 use std::cell::RefCell;
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct Node {
     value: i32,
     parent: RcWeak<RefCell<Node>>,
@@ -143,11 +143,11 @@ for child in &decoded.borrow().children {
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
 use fory::ArcWeak;
 use std::sync::{Arc, Mutex};
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Node {
     val: i32,
     parent: ArcWeak<Mutex<Node>>,
diff --git a/docs/guide/rust/row-format.md b/docs/guide/rust/row-format.md
index 1728d4551..c0728f746 100644
--- a/docs/guide/rust/row-format.md
+++ b/docs/guide/rust/row-format.md
@@ -110,9 +110,9 @@ assert_eq!(prefs.values().get(0), "en");
 | Memory usage         | Full object graph in memory   | Only accessed fields in memory  |
 | Suitable for         | Small objects, full access    | Large objects, selective access |
 
-## ForyRow vs ForyObject
+## ForyRow vs ForyStruct
 
-| Feature         | `#[derive(ForyRow)]`  | `#[derive(ForyObject)]`    |
+| Feature         | `#[derive(ForyRow)]`  | `#[derive(ForyStruct)]`    |
 | --------------- | --------------------- | -------------------------- |
 | Deserialization | Zero-copy, lazy       | Full object reconstruction |
 | Field access    | Direct from binary    | Normal struct access       |
diff --git a/docs/guide/rust/schema-evolution.md b/docs/guide/rust/schema-evolution.md
index 6b51ba1ac..e6dbfced1 100644
--- a/docs/guide/rust/schema-evolution.md
+++ b/docs/guide/rust/schema-evolution.md
@@ -27,17 +27,17 @@ Enable schema evolution with `compatible(true)`:
 
 ```rust
 use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
 use std::collections::HashMap;
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct PersonV1 {
     name: String,
     age: i32,
     address: String,
 }
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct PersonV2 {
     name: String,
     age: i32,
@@ -74,9 +74,9 @@ assert_eq!(person_v2.phone, None);
 If a struct schema is stable and will not change, you can disable evolution for that struct to avoid compatible metadata overhead. Use `#[fory(evolving = false)]`:
 
 ```rust
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 #[fory(evolving = false)]
 struct StableMessage {
     id: i32,
@@ -108,9 +108,9 @@ Apache Fory™ supports three types of enum variants with full schema evolution
 - **Named**: Struct-like variants (`Event::Click { x: i32, y: i32 }`)
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(Default, ForyObject, Debug, PartialEq)]
+#[derive(Default, ForyStruct, Debug, PartialEq)]
 enum Value {
     #[default]
     Null,
@@ -136,17 +136,17 @@ Compatible mode enables robust schema evolution with variant type encoding (2 bi
 - `0b0` = Unit, `0b1` = Unnamed, `0b10` = Named
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
 // Old version
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 enum OldEvent {
     Click { x: i32, y: i32 },
     Scroll { delta: f64 },
 }
 
 // New version - added field and variant
-#[derive(Default, ForyObject)]
+#[derive(Default, ForyStruct)]
 enum NewEvent {
     #[default]
     Unknown,
diff --git a/docs/guide/rust/troubleshooting.md b/docs/guide/rust/troubleshooting.md
index 6d99c551b..90afa1369 100644
--- a/docs/guide/rust/troubleshooting.md
+++ b/docs/guide/rust/troubleshooting.md
@@ -68,10 +68,10 @@ Reset the variable afterwards to avoid aborting user-facing code paths.
 
 ### Struct Field Tracing
 
-Add the `#[fory(debug)]` attribute alongside `#[derive(ForyObject)]` to emit hook invocations:
+Add the `#[fory(debug)]` attribute alongside `#[derive(ForyStruct)]` to emit hook invocations:
 
 ```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 #[fory(debug)]
 struct MyStruct {
     field1: i32,
diff --git a/docs/guide/rust/type-registration.md b/docs/guide/rust/type-registration.md
index d44d0094a..d2849ac88 100644
--- a/docs/guide/rust/type-registration.md
+++ b/docs/guide/rust/type-registration.md
@@ -27,9 +27,9 @@ Register types with a numeric ID for fast, compact serialization:
 
 ```rust
 use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct User {
     name: String,
     age: i32,
diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md
index 5bd3494e0..516039f3f 100644
--- a/docs/guide/xlang/field-nullability.md
+++ b/docs/guide/xlang/field-nullability.md
@@ -137,9 +137,9 @@ fory.register_type(Person, typename="example.Person")
 ### Rust
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Person {
     // Non-nullable by default
     name: String,
diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md
index d7729c240..6b5b28698 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -163,10 +163,10 @@ To disable reference tracking for C++ entirely, set
 #### Rust: Field Attributes
 
 ```rust
-use fory::ForyObject;
+use fory::ForyStruct;
 use std::rc::Rc;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Document {
     title: String,
 
diff --git a/docs/guide/xlang/getting-started.md b/docs/guide/xlang/getting-started.md
index 9cc41d5fe..9ebb01f6c 100644
--- a/docs/guide/xlang/getting-started.md
+++ b/docs/guide/xlang/getting-started.md
@@ -165,9 +165,9 @@ fory.RegisterNamedStruct(Person{}, "example.Person")
 **Rust:**
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Person {
     name: String,
     age: i32,
diff --git a/docs/guide/xlang/serialization.md b/docs/guide/xlang/serialization.md
index 17fbd5c5c..1257307db 100644
--- a/docs/guide/xlang/serialization.md
+++ b/docs/guide/xlang/serialization.md
@@ -359,17 +359,17 @@ console.log(result);
 
 ```rust
 use chrono::{NaiveDate, NaiveDateTime};
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 use std::collections::HashMap;
 
 #[test]
 fn complex_struct() {
-    #[derive(ForyObject, Debug, PartialEq)]
+    #[derive(ForyStruct, Debug, PartialEq)]
     struct Animal {
         category: String,
     }
 
-    #[derive(ForyObject, Debug, PartialEq)]
+    #[derive(ForyStruct, Debug, PartialEq)]
     struct Person {
         c1: Vec<u8>,  // binary
         c2: Vec<i16>, // primitive array
diff --git a/rust/README.md b/rust/README.md
index 897e24408..6fd35dc00 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -40,9 +40,9 @@ fory = "0.16"
 
 ```rust
 use fory::{Fory, Error, Reader};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct User {
     name: String,
     age: i32,
@@ -80,7 +80,7 @@ fn main() -> Result<(), Error> {
 
 ### 1. Object Graph Serialization
 
-Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyObject)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
+Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyStruct)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
 
 **Key capabilities:**
 
@@ -92,10 +92,10 @@ Apache Fory™ provides automatic serialization of complex object graphs, preser
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
 use std::collections::HashMap;
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct Person {
     name: String,
     age: i32,
@@ -104,7 +104,7 @@ struct Person {
     metadata: HashMap<String, String>,
 }
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
 struct Address {
     street: String,
     city: String,
@@ -184,12 +184,12 @@ To serialize circular references like parent-child relationships or doubly-linke
 
 ```rust
 use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
 use fory::RcWeak;
 use std::rc::Rc;
 use std::cell::RefCell;
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct Node {
     value: i32,
     parent: RcWeak<RefCell<Node>>,
@@ -250,14 +250,14 @@ Apache Fory™ supports polymorphic serialization through trait objects, enablin
 ```rust
 use fory::{Fory, register_trait_type};
 use fory::Serializer;
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
 
 trait Animal: Serializer {
     fn speak(&self) -> String;
     fn name(&self) -> &str;
 }
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Dog { name: String, breed: String }
 
 impl Animal for Dog {
@@ -265,7 +265,7 @@ impl Animal for Dog {
     fn name(&self) -> &str { &self.name }
 }
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Cat { name: String, color: String }
 
 impl Animal for Cat {
@@ -276,7 +276,7 @@ impl Animal for Cat {
 // Register trait implementations
 register_trait_type!(Animal, Dog, Cat);
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Zoo {
     star_animal: Box<dyn Animal>,
 }
@@ -320,17 +320,17 @@ Apache Fory™ supports schema evolution in **Compatible mode**, allowing serial
 
 ```rust
 use fory::Fory;
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
 use std::collections::HashMap;
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct PersonV1 {
     name: String,
     age: i32,
     address: String,
 }
 
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
 struct PersonV2 {
     name: String,
     age: i32,
@@ -380,9 +380,9 @@ Apache Fory™ supports three types of enum variants with full schema evolution
 - Automatic type mismatch handling
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyEnum, ForyStruct, ForyUnion};
 
-#[derive(Default, ForyObject, Debug, PartialEq)]
+#[derive(Default, ForyStruct, Debug, PartialEq)]
 enum Value {
     #[default]
     Null,
@@ -449,7 +449,7 @@ assert_eq!(data, decoded);
 
 ### 7. Custom Serializers
 
-For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` trait manually. This is useful for:
+For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` trait manually. This is useful for:
 
 - External types from other crates
 - Types with special serialization requirements
diff --git a/rust/fory-derive/src/object/mod.rs b/rust/fory-core/build.rs
similarity index 73%
copy from rust/fory-derive/src/object/mod.rs
copy to rust/fory-core/build.rs
index d546adb45..fbd23e892 100644
--- a/rust/fory-derive/src/object/mod.rs
+++ b/rust/fory-core/build.rs
@@ -15,12 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod derive_enum;
-pub(crate) mod field_meta;
-mod misc;
-pub(crate) mod read;
-mod serializer;
-pub(crate) mod util;
-mod write;
+fn main() {
+    println!("cargo:rerun-if-env-changed=ENABLE_FORY_DEBUG_OUTPUT");
 
-pub use serializer::derive_serializer;
+    if matches!(
+        std::env::var("ENABLE_FORY_DEBUG_OUTPUT").as_deref(),
+        Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("on")
+    ) {
+        println!("cargo:rustc-cfg=fory_debug_output");
+    }
+}
diff --git a/rust/fory-core/src/context.rs b/rust/fory-core/src/context.rs
index 059f8935b..acd567029 100644
--- a/rust/fory-core/src/context.rs
+++ b/rust/fory-core/src/context.rs
@@ -26,6 +26,7 @@ use crate::resolver::meta_resolver::{MetaReaderResolver, MetaWriterResolver};
 use crate::resolver::meta_string_resolver::{MetaStringReaderResolver, MetaStringWriterResolver};
 use crate::resolver::{RefReader, RefWriter};
 use crate::resolver::{TypeInfo, TypeResolver};
+use crate::serializer::StructSerializer;
 use crate::type_id as types;
 use crate::TypeId;
 use std::rc::Rc;
@@ -222,6 +223,47 @@ impl<'a> WriteContext<'a> {
             .write_type_meta(&mut self.writer, type_id, &self.type_resolver)
     }
 
+    /// Write generated struct type info without Rust TypeId hash lookups.
+    #[inline(always)]
+    pub fn write_struct_type_info<T: StructSerializer>(&mut self) -> Result<(), Error> {
+        let rust_type_id = std::any::TypeId::of::<T>();
+        let type_index = T::fory_type_index();
+        let type_id = self.type_resolver.get_type_id_by_index(type_index)?;
+        match type_id {
+            TypeId::STRUCT | TypeId::ENUM | TypeId::EXT | TypeId::TYPED_UNION => {
+                self.writer.write_u8(type_id as u8);
+                let user_type_id = self
+                    .type_resolver
+                    .get_user_type_id_by_index(&rust_type_id, type_index)?;
+                self.writer.write_var_u32(user_type_id);
+            }
+            TypeId::COMPATIBLE_STRUCT | TypeId::NAMED_COMPATIBLE_STRUCT => {
+                self.writer.write_u8(type_id as u8);
+                self.meta_resolver.write_type_meta_fast(
+                    &mut self.writer,
+                    rust_type_id,
+                    type_index,
+                    &self.type_resolver,
+                )?;
+            }
+            TypeId::NAMED_ENUM | TypeId::NAMED_EXT | TypeId::NAMED_STRUCT | TypeId::NAMED_UNION
+                if self.is_share_meta() =>
+            {
+                self.writer.write_u8(type_id as u8);
+                self.meta_resolver.write_type_meta_fast(
+                    &mut self.writer,
+                    rust_type_id,
+                    type_index,
+                    &self.type_resolver,
+                )?;
+            }
+            _ => {
+                self.write_any_type_info(type_id as u32, rust_type_id)?;
+            }
+        }
+        Ok(())
+    }
+
     pub fn write_any_type_info(
         &mut self,
         fory_type_id: u32,
diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs
index bc7953441..f82d2251c 100644
--- a/rust/fory-core/src/fory.rs
+++ b/rust/fory-core/src/fory.rs
@@ -360,9 +360,9 @@ impl ForyBuilder {
 ///
 /// ```rust, ignore
 /// use fory::Fory;
-/// use fory::ForyObject;
+/// use fory::{ForyEnum, ForyStruct, ForyUnion};
 ///
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
 /// struct User {
 ///     name: String,
 ///     age: u32,
@@ -519,9 +519,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory::Fory;
-    /// use fory::ForyObject;
+    /// use fory::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct Point { x: i32, y: i32 }
     ///
     /// let fory = Fory::default();
@@ -573,9 +573,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory_core::Fory;
-    /// use fory_derive::ForyObject;
+    /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct Point {
     ///     x: i32,
     ///     y: i32,
@@ -593,9 +593,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory_core::Fory;
-    /// use fory_derive::ForyObject;
+    /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject, PartialEq, Debug)]
+    /// #[derive(ForyStruct, PartialEq, Debug)]
     /// struct Point {
     ///     x: i32,
     ///     y: i32,
@@ -634,9 +634,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory_core::Fory;
-    /// use fory_derive::ForyObject;
+    /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct Point {
     ///     x: i32,
     ///     y: i32,
@@ -774,9 +774,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory::Fory;
-    /// use fory::ForyObject;
+    /// use fory::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct User { name: String, age: u32 }
     ///
     /// let mut fory = Fory::default();
@@ -823,9 +823,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory::Fory;
-    /// use fory::ForyObject;
+    /// use fory::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct User { name: String, age: u32 }
     ///
     /// let mut fory = Fory::default();
@@ -872,9 +872,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory::Fory;
-    /// use fory::ForyObject;
+    /// use fory::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct User { name: String, age: u32 }
     ///
     /// let mut fory = Fory::default();
@@ -1028,9 +1028,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory::Fory;
-    /// use fory::ForyObject;
+    /// use fory::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct Point { x: i32, y: i32 }
     ///
     /// let fory = Fory::default();
@@ -1081,9 +1081,9 @@ impl Fory {
     ///
     /// ```rust, ignore
     /// use fory_core::{Fory, Reader};
-    /// use fory_derive::ForyObject;
+    /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
     ///
-    /// #[derive(ForyObject)]
+    /// #[derive(ForyStruct)]
     /// struct Point { x: i32, y: i32 }
     ///
     /// let fory = Fory::default();
diff --git a/rust/fory-core/src/lib.rs b/rust/fory-core/src/lib.rs
index 272cbd097..4d1ac219b 100644
--- a/rust/fory-core/src/lib.rs
+++ b/rust/fory-core/src/lib.rs
@@ -73,16 +73,16 @@
 //!
 //! ```rust,ignore
 //! use fory_core::{Fory, register_trait_type, Serializer};
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
 //!
 //! trait Animal: Serializer {
 //!     fn speak(&self) -> String;
 //! }
 //!
-//! #[derive(ForyObject, Debug)]
+//! #[derive(ForyStruct, Debug)]
 //! struct Dog { name: String }
 //!
-//! #[derive(ForyObject, Debug)]
+//! #[derive(ForyStruct, Debug)]
 //! struct Cat { name: String }
 //!
 //! impl Animal for Dog {
@@ -95,7 +95,7 @@
 //!
 //! register_trait_type!(Animal, Dog, Cat);
 //!
-//! #[derive(ForyObject)]
+//! #[derive(ForyStruct)]
 //! struct Zoo {
 //!     star_animal: Box<dyn Animal>,
 //! }
@@ -122,11 +122,11 @@
 //!
 //! ```rust,ignore
 //! # use fory_core::Serializer;
-//! # use fory_derive::ForyObject;
+//! # use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
 //! # use std::rc::Rc;
 //! # use std::sync::Arc;
 //! # trait Animal: Serializer { fn speak(&self) -> String; }
-//! #[derive(ForyObject)]
+//! #[derive(ForyStruct)]
 //! struct Shelter {
 //!     animals_rc: Vec<Rc<dyn Animal>>,
 //!     animals_arc: Vec<Arc<dyn Animal>>,
diff --git a/rust/fory-core/src/meta/type_meta.rs b/rust/fory-core/src/meta/type_meta.rs
index 035e7cb32..fd14d4603 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -96,41 +96,53 @@ static FIELD_NAME_ENCODINGS: &[Encoding] = &[
     Encoding::LowerUpperDigitSpecial,
 ];
 
-#[derive(Debug, Eq, Clone)]
+#[derive(Eq, Clone)]
 pub struct FieldType {
     pub type_id: u32,
     pub user_type_id: u32,
     pub nullable: bool,
     pub track_ref: bool,
     pub generics: Vec<FieldType>,
+    compatible_fingerprint: u64,
 }
 
 impl FieldType {
     pub fn new(type_id: u32, nullable: bool, generics: Vec<FieldType>) -> Self {
-        FieldType {
-            type_id,
-            user_type_id: NO_USER_TYPE_ID,
-            nullable,
-            track_ref: false,
-            generics,
-        }
+        Self::new_with_user_type_id(type_id, NO_USER_TYPE_ID, nullable, false, generics)
     }
 
-    pub fn new_with_ref(
+    pub(crate) fn new_with_user_type_id(
         type_id: u32,
+        user_type_id: u32,
         nullable: bool,
         track_ref: bool,
         generics: Vec<FieldType>,
     ) -> Self {
-        FieldType {
+        let compatible_fingerprint = compute_field_type_fingerprint(type_id, &generics);
+        Self {
             type_id,
-            user_type_id: NO_USER_TYPE_ID,
+            user_type_id,
             nullable,
             track_ref,
             generics,
+            compatible_fingerprint,
         }
     }
 
+    pub fn new_with_ref(
+        type_id: u32,
+        nullable: bool,
+        track_ref: bool,
+        generics: Vec<FieldType>,
+    ) -> Self {
+        Self::new_with_user_type_id(type_id, NO_USER_TYPE_ID, nullable, track_ref, generics)
+    }
+
+    #[inline(always)]
+    pub(crate) fn compatible_fingerprint(&self) -> u64 {
+        self.compatible_fingerprint
+    }
+
     fn to_bytes(&self, writer: &mut Writer, write_flag: bool, nullable: bool) -> Result<(), Error> {
         let mut header = self.type_id;
         if header == NAMED_ENUM {
@@ -203,32 +215,28 @@ impl FieldType {
         Ok(match type_id {
             x if x == TypeId::LIST as u32 || x == TypeId::SET as u32 => {
                 let generic = Self::from_bytes(reader, true, None)?;
-                Self {
+                Self::new_with_user_type_id(
                     type_id,
                     user_type_id,
-                    nullable: _nullable,
-                    track_ref: _ref_tracking,
-                    generics: vec![generic],
-                }
+                    _nullable,
+                    _ref_tracking,
+                    vec![generic],
+                )
             }
             x if x == TypeId::MAP as u32 => {
                 let key_generic = Self::from_bytes(reader, true, None)?;
                 let val_generic = Self::from_bytes(reader, true, None)?;
-                Self {
+                Self::new_with_user_type_id(
                     type_id,
                     user_type_id,
-                    nullable: _nullable,
-                    track_ref: _ref_tracking,
-                    generics: vec![key_generic, val_generic],
-                }
+                    _nullable,
+                    _ref_tracking,
+                    vec![key_generic, val_generic],
+                )
+            }
+            _ => {
+                Self::new_with_user_type_id(type_id, user_type_id, _nullable, _ref_tracking, vec![])
             }
-            _ => Self {
-                type_id,
-                user_type_id,
-                nullable: _nullable,
-                track_ref: _ref_tracking,
-                generics: vec![],
-            },
         })
     }
 }
@@ -397,6 +405,69 @@ fn fnv1a_hash_u32(hash: u64, value: u32) -> u64 {
     fnv1a_hash_bytes(hash, &value.to_le_bytes())
 }
 
+#[inline(always)]
+fn fnv1a_hash_u64(hash: u64, value: u64) -> u64 {
+    fnv1a_hash_bytes(hash, &value.to_le_bytes())
+}
+
+#[inline(always)]
+fn compatible_fingerprint_type_id(type_id: u32) -> u32 {
+    match type_id {
+        _ if type_id == STRUCT
+            || type_id == COMPATIBLE_STRUCT
+            || type_id == NAMED_STRUCT
+            || type_id == NAMED_COMPATIBLE_STRUCT
+            || type_id == UNKNOWN =>
+        {
+            STRUCT
+        }
+        _ if type_id == ENUM || type_id == NAMED_ENUM => ENUM,
+        _ if type_id == EXT || type_id == NAMED_EXT => EXT,
+        _ if type_id == BINARY || type_id == INT8_ARRAY || type_id == UINT8_ARRAY => BINARY,
+        _ if type_id == TypeId::INT32 as u32 || type_id == TypeId::VARINT32 as u32 => {
+            TypeId::VARINT32 as u32
+        }
+        _ if type_id == TypeId::INT64 as u32
+            || type_id == TypeId::VARINT64 as u32
+            || type_id == TypeId::TAGGED_INT64 as u32 =>
+        {
+            TypeId::VARINT64 as u32
+        }
+        _ if type_id == TypeId::UINT32 as u32 || type_id == TypeId::VAR_UINT32 as u32 => {
+            TypeId::VAR_UINT32 as u32
+        }
+        _ if type_id == TypeId::UINT64 as u32
+            || type_id == TypeId::VAR_UINT64 as u32
+            || type_id == TypeId::TAGGED_UINT64 as u32 =>
+        {
+            TypeId::VAR_UINT64 as u32
+        }
+        _ => type_id,
+    }
+}
+
+#[inline(always)]
+fn compute_field_type_fingerprint(type_id: u32, generics: &[FieldType]) -> u64 {
+    let mut hash = fnv1a_hash_u32(FNV_OFFSET_BASIS, compatible_fingerprint_type_id(type_id));
+    hash = fnv1a_hash_u32(hash, generics.len() as u32);
+    for generic in generics {
+        hash = fnv1a_hash_u64(hash, generic.compatible_fingerprint());
+    }
+    hash
+}
+
+impl std::fmt::Debug for FieldType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("FieldType")
+            .field("type_id", &self.type_id)
+            .field("user_type_id", &self.user_type_id)
+            .field("nullable", &self.nullable)
+            .field("track_ref", &self.track_ref)
+            .field("generics", &self.generics)
+            .finish()
+    }
+}
+
 #[inline(always)]
 fn hash_field_type(mut hash: u64, field_type: &FieldType) -> u64 {
     let type_id = normalize_type_id_for_eq(field_type.type_id);
@@ -951,25 +1022,15 @@ impl TypeMeta {
                     if field.field_name.is_empty() {
                         field.field_name = local_info.field_name.clone();
                     }
-                    // Use FieldType comparison which normalizes type IDs for cross-language
-                    // schema evolution (e.g., UNKNOWN=0 matches STRUCT variants)
-                    if field.field_type != local_info.field_type {
-                        if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
-                            eprintln!(
-                                "[fory-debug] field type mismatch: name={}, remote_type={:?}, local_type={:?}",
-                                field.field_name, field.field_type, local_info.field_type
-                            );
-                        }
-                        field.field_id = -1; // Type mismatch, skip
-                    } else {
-                        // Assign SORTED INDEX for matching in generated code
-                        field.field_id = sorted_index as i16;
-                        if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
-                            eprintln!(
-                                "[fory-debug]   matched field: name={}, assigned_field_id={}",
-                                field.field_name, field.field_id
-                            );
-                        }
+                    // Assign SORTED INDEX for generated code. The generated field
+                    // codec inspects the remote FieldType and either consumes it or
+                    // asks the caller to skip the remote payload.
+                    field.field_id = sorted_index as i16;
+                    if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
+                        eprintln!(
+                            "[fory-debug]   matched field: name={}, assigned_field_id={}, remote_type={:?}, local_type={:?}",
+                            field.field_name, field.field_id, field.field_type, local_info.field_type
+                        );
                     }
                 }
                 None => {
diff --git a/rust/fory-core/src/resolver/meta_resolver.rs b/rust/fory-core/src/resolver/meta_resolver.rs
index 38e07e457..43f9851f4 100644
--- a/rust/fory-core/src/resolver/meta_resolver.rs
+++ b/rust/fory-core/src/resolver/meta_resolver.rs
@@ -30,9 +30,12 @@ use std::rc::Rc;
 #[derive(Default)]
 pub struct MetaWriterResolver {
     type_id_index_map: HashMap<std::any::TypeId, usize>,
+    type_index_index_map: Vec<usize>,
+    next_index: usize,
 }
 
 const MAX_PARSED_NUM_TYPE_DEFS: usize = 8192;
+const NO_WRITTEN_TYPE_INDEX: usize = usize::MAX;
 
 #[allow(dead_code)]
 impl MetaWriterResolver {
@@ -52,7 +55,8 @@ impl MetaWriterResolver {
             }
             None => {
                 // New type: index << 1, LSB=0, followed by TypeMeta bytes inline
-                let index = self.type_id_index_map.len();
+                let index = self.next_index;
+                self.next_index += 1;
                 writer.write_var_u32((index as u32) << 1);
                 self.type_id_index_map.insert(type_id, index);
                 // Write TypeMeta bytes inline
@@ -63,9 +67,41 @@ impl MetaWriterResolver {
         Ok(())
     }
 
+    /// Write type meta by generated struct type index, avoiding Rust TypeId hash lookup.
+    #[inline(always)]
+    pub fn write_type_meta_fast(
+        &mut self,
+        writer: &mut Writer,
+        type_id: std::any::TypeId,
+        type_index: u32,
+        type_resolver: &TypeResolver,
+    ) -> Result<(), Error> {
+        let type_index = type_index as usize;
+        if let Some(&index) = self.type_index_index_map.get(type_index) {
+            if index != NO_WRITTEN_TYPE_INDEX {
+                writer.write_var_u32(((index as u32) << 1) | 1);
+                return Ok(());
+            }
+        }
+
+        let index = self.next_index;
+        self.next_index += 1;
+        writer.write_var_u32((index as u32) << 1);
+        if type_index >= self.type_index_index_map.len() {
+            self.type_index_index_map
+                .resize(type_index + 1, NO_WRITTEN_TYPE_INDEX);
+        }
+        self.type_index_index_map[type_index] = index;
+        let type_meta = type_resolver.get_type_meta_by_index_ref(&type_id, type_index as u32)?;
+        writer.write_bytes(type_meta.get_bytes());
+        Ok(())
+    }
+
     #[inline(always)]
     pub fn reset(&mut self) {
         self.type_id_index_map.clear();
+        self.type_index_index_map.clear();
+        self.next_index = 0;
     }
 }
 
diff --git a/rust/fory-core/src/serializer/arc.rs b/rust/fory-core/src/serializer/arc.rs
index 2277078a3..a67746820 100644
--- a/rust/fory-core/src/serializer/arc.rs
+++ b/rust/fory-core/src/serializer/arc.rs
@@ -135,10 +135,7 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for Arc<T>
     }
 
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/serializer/box_.rs b/rust/fory-core/src/serializer/box_.rs
index 2e5c02197..da7a378eb 100644
--- a/rust/fory-core/src/serializer/box_.rs
+++ b/rust/fory-core/src/serializer/box_.rs
@@ -59,10 +59,7 @@ impl<T: Serializer + ForyDefault> Serializer for Box<T> {
 
     #[inline(always)]
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     #[inline(always)]
diff --git a/rust/fory-core/src/serializer/codec.rs b/rust/fory-core/src/serializer/codec.rs
new file mode 100644
index 000000000..9c6a74ee8
--- /dev/null
+++ b/rust/fory-core/src/serializer/codec.rs
@@ -0,0 +1,2547 @@
+// 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.
+
+//! Internal field codecs used by macro-generated serializers.
+//!
+//! User-owned custom serialization belongs in [`crate::Serializer`]. Codecs are
+//! Fory-owned building blocks that allow generated code to apply field-local and
+//! nested collection configuration without creating wrapper value types.
+
+use crate::context::{ReadContext, WriteContext};
+use crate::error::Error;
+use crate::meta::FieldType;
+use crate::resolver::{RefFlag, RefMode, TypeResolver};
+use crate::serializer::{ForyDefault, Serializer};
+use crate::type_id::{self, need_to_write_type_for_field, TypeId, SIZE_OF_REF_AND_TYPE, UNKNOWN};
+use std::any::Any;
+use std::collections::HashMap;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::rc::Rc;
+use std::sync::Arc;
+
+const TRACKING_REF: u8 = 0b1;
+const HAS_NULL: u8 = 0b10;
+const DECL_ELEMENT_TYPE: u8 = 0b100;
+const IS_SAME_TYPE: u8 = 0b1000;
+
+const TRACKING_KEY_REF: u8 = 0b1;
+const KEY_NULL: u8 = 0b10;
+const DECL_KEY_TYPE: u8 = 0b100;
+const TRACKING_VALUE_REF: u8 = 0b1000;
+const VALUE_NULL: u8 = 0b10000;
+const DECL_VALUE_TYPE: u8 = 0b100000;
+
+const MAX_CHUNK_SIZE: u8 = 255;
+
+#[inline(always)]
+pub fn field_ref_mode(field_type: &FieldType) -> RefMode {
+    if field_type.track_ref {
+        RefMode::Tracking
+    } else if crate::serializer::util::field_need_write_ref_into(
+        field_type.type_id,
+        field_type.nullable,
+    ) {
+        RefMode::NullOnly
+    } else {
+        RefMode::None
+    }
+}
+
+#[inline(always)]
+fn field_read_type_info<T: Serializer>(context: &ReadContext, field_type: &FieldType) -> bool {
+    if context.is_compatible() {
+        crate::serializer::util::field_need_read_type_info(field_type.type_id)
+    } else {
+        T::fory_is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn field_write_type_info<T: Serializer>(context: &WriteContext) -> bool {
+    if context.is_compatible() {
+        need_to_write_type_for_field(T::fory_static_type_id())
+    } else {
+        T::fory_is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn serializer_static_field_type_id<T: Serializer>() -> u32 {
+    let type_id = T::fory_static_type_id() as u32;
+    if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+        TypeId::UNION as u32
+    } else {
+        type_id
+    }
+}
+
+#[inline(always)]
+fn serializer_ref_mode<T: Serializer, const NULLABLE: bool, const TRACK_REF: bool>() -> RefMode {
+    if TRACK_REF {
+        RefMode::Tracking
+    } else if crate::serializer::util::field_need_write_ref_into(
+        serializer_static_field_type_id::<T>(),
+        NULLABLE,
+    ) {
+        RefMode::NullOnly
+    } else {
+        RefMode::None
+    }
+}
+
+#[inline(always)]
+fn serializer_read_type_info<T: Serializer>(context: &ReadContext) -> bool {
+    if context.is_compatible() {
+        crate::serializer::util::field_need_read_type_info(serializer_static_field_type_id::<T>())
+    } else {
+        T::fory_is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn codec_read_type_info<T, C>(context: &ReadContext, field_type: &FieldType) -> bool
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if context.is_compatible() {
+        field_type.type_id == type_id::UNKNOWN
+            || crate::serializer::util::field_need_read_type_info(field_type.type_id)
+    } else {
+        C::is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn codec_static_field_type_id<T, C>() -> u32
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    let type_id = C::static_type_id() as u32;
+    if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+        TypeId::UNION as u32
+    } else {
+        type_id
+    }
+}
+
+#[inline(always)]
+fn codec_read_type_info_static<T, C>(context: &ReadContext) -> bool
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if context.is_compatible() {
+        let type_id = codec_static_field_type_id::<T, C>();
+        type_id == type_id::UNKNOWN || crate::serializer::util::field_need_read_type_info(type_id)
+    } else {
+        C::is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn codec_write_type_info<T, C>(context: &WriteContext) -> bool
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if context.is_compatible() {
+        need_to_write_type_for_field(C::static_type_id())
+    } else {
+        C::is_polymorphic()
+    }
+}
+
+#[inline(always)]
+fn same_numeric_family(local: u32, remote: u32) -> bool {
+    matches!(
+        (local, remote),
+        (type_id::INT32, type_id::INT32 | type_id::VARINT32)
+            | (type_id::VARINT32, type_id::INT32 | type_id::VARINT32)
+            | (
+                type_id::INT64,
+                type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+            )
+            | (
+                type_id::VARINT64,
+                type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+            )
+            | (
+                type_id::TAGGED_INT64,
+                type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+            )
+            | (type_id::UINT32, type_id::UINT32 | type_id::VAR_UINT32)
+            | (type_id::VAR_UINT32, type_id::UINT32 | type_id::VAR_UINT32)
+            | (
+                type_id::UINT64,
+                type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+            )
+            | (
+                type_id::VAR_UINT64,
+                type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+            )
+            | (
+                type_id::TAGGED_UINT64,
+                type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+            )
+    )
+}
+
+#[inline(always)]
+fn collection_type_with_fallback_generics(type_id: u32) -> bool {
+    type_id == type_id::LIST || type_id == type_id::SET || type_id == type_id::MAP
+}
+
+#[inline(always)]
+pub fn field_types_compatible(local: &FieldType, remote: &FieldType) -> bool {
+    if local.compatible_fingerprint() == remote.compatible_fingerprint() {
+        return true;
+    }
+    if local.type_id == remote.type_id
+        && collection_type_with_fallback_generics(local.type_id)
+        && (local.generics.is_empty() || remote.generics.is_empty())
+    {
+        return true;
+    }
+    false
+}
+
+#[inline(always)]
+fn generic_field_type<'a>(
+    field_type: &'a FieldType,
+    index: usize,
+    owner: &str,
+) -> Result<&'a FieldType, Error> {
+    field_type.generics.get(index).ok_or_else(|| {
+        Error::invalid_data(format!(
+            "{owner} field metadata is missing generic type at index {index}"
+        ))
+    })
+}
+
+#[inline(always)]
+fn field_type_for_serializer<T: Serializer>(
+    type_resolver: &TypeResolver,
+    nullable: bool,
+    track_ref: bool,
+) -> Result<FieldType, Error> {
+    let mut type_id = T::fory_get_type_id(type_resolver)? as u32;
+    let mut user_type_id = u32::MAX;
+    if !type_id::is_internal_type(type_id) {
+        let type_info = T::fory_get_type_info(type_resolver)?;
+        type_id = type_info.get_type_id() as u32;
+        user_type_id = type_info.get_user_type_id();
+        if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+            type_id = TypeId::UNION as u32;
+            user_type_id = u32::MAX;
+        }
+    } else if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+        type_id = TypeId::UNION as u32;
+    }
+    Ok(FieldType::new_with_user_type_id(
+        type_id,
+        user_type_id,
+        nullable,
+        track_ref,
+        Vec::new(),
+    ))
+}
+
+pub trait Codec<T: 'static>: 'static {
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error>;
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        std::mem::size_of::<T>()
+    }
+
+    fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error>;
+
+    fn read_field(context: &mut ReadContext) -> Result<T, Error>;
+
+    fn read_compatible(
+        context: &mut ReadContext,
+        local_field_type: &FieldType,
+        remote_field_type: &FieldType,
+    ) -> Result<Option<T>, Error> {
+        if !field_types_compatible(local_field_type, remote_field_type) {
+            return Ok(None);
+        }
+        Self::read_field_with_type(context, remote_field_type).map(Some)
+    }
+
+    fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error>;
+
+    fn read_data(context: &mut ReadContext) -> Result<T, Error>;
+
+    #[inline(always)]
+    fn read_data_with_type(
+        context: &mut ReadContext,
+        _remote_data_type: &FieldType,
+    ) -> Result<T, Error> {
+        Self::read_data(context)
+    }
+
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<T, Error>;
+
+    fn write_with_mode(
+        value: &T,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        has_generics: bool,
+    ) -> Result<(), Error>;
+
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<T, Error>;
+
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<T, Error>;
+
+    fn default_value() -> T;
+
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error>;
+
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error>;
+
+    #[inline(always)]
+    fn read_type_info_as_field_type(context: &mut ReadContext) -> Result<FieldType, Error> {
+        Self::read_type_info(context)?;
+        Self::field_type(context.get_type_resolver())
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        TypeId::UNKNOWN
+    }
+
+    #[inline(always)]
+    fn is_option() -> bool {
+        false
+    }
+
+    #[inline(always)]
+    fn is_none(_value: &T) -> bool {
+        false
+    }
+
+    #[inline(always)]
+    fn is_polymorphic() -> bool {
+        false
+    }
+
+    #[inline(always)]
+    fn is_shared_ref() -> bool {
+        false
+    }
+
+    #[inline(always)]
+    fn concrete_type_id(value: &T) -> std::any::TypeId {
+        let _ = value;
+        std::any::TypeId::of::<T>()
+    }
+}
+
+pub struct SerializerCodec<T, const NULLABLE: bool, const TRACK_REF: bool>(PhantomData<T>);
+
+impl<T, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+    for SerializerCodec<T, NULLABLE, TRACK_REF>
+where
+    T: Serializer + ForyDefault,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        field_type_for_serializer::<T>(type_resolver, NULLABLE, TRACK_REF)
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+    }
+
+    #[inline(always)]
+    fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write(
+            value,
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            field_write_type_info::<T>(context),
+            false,
+        )
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            serializer_read_type_info::<T>(context),
+        )
+    }
+
+    #[inline(always)]
+    fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write_data_generic(value, context, false)
+    }
+
+    #[inline(always)]
+    fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read_data(context)
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            field_ref_mode(remote_field_type),
+            field_read_type_info::<T>(context, remote_field_type),
+        )
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &T,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        has_generics: bool,
+    ) -> Result<(), Error> {
+        T::fory_write(value, context, ref_mode, write_type_info, has_generics)
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<T, Error> {
+        T::fory_read(context, ref_mode, read_type_info)
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<T, Error> {
+        T::fory_read_with_type_info(context, ref_mode, type_info)
+    }
+
+    #[inline(always)]
+    fn default_value() -> T {
+        T::fory_default()
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write_type_info(context)
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        T::fory_read_type_info(context)
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        T::fory_static_type_id()
+    }
+
+    #[inline(always)]
+    fn is_option() -> bool {
+        T::fory_is_option()
+    }
+
+    #[inline(always)]
+    fn is_none(value: &T) -> bool {
+        value.fory_is_none()
+    }
+
+    #[inline(always)]
+    fn is_polymorphic() -> bool {
+        T::fory_is_polymorphic()
+    }
+
+    #[inline(always)]
+    fn is_shared_ref() -> bool {
+        T::fory_is_shared_ref()
+    }
+
+    #[inline(always)]
+    fn concrete_type_id(value: &T) -> std::any::TypeId {
+        value.fory_concrete_type_id()
+    }
+}
+
+pub struct CollectionSerializerCodec<
+    T,
+    E,
+    EC,
+    const TYPE_ID: u8,
+    const NULLABLE: bool,
+    const TRACK_REF: bool,
+>(PhantomData<(T, E, EC)>);
+
+impl<T, E, EC, const TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+    for CollectionSerializerCodec<T, E, EC, TYPE_ID, NULLABLE, TRACK_REF>
+where
+    T: Serializer + ForyDefault,
+    E: 'static,
+    EC: Codec<E>,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        Ok(FieldType::new_with_ref(
+            TYPE_ID as u32,
+            NULLABLE,
+            TRACK_REF,
+            vec![EC::field_type(type_resolver)?],
+        ))
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+    }
+
+    #[inline(always)]
+    fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write(
+            value,
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            field_write_type_info::<T>(context),
+            true,
+        )
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            serializer_read_type_info::<T>(context),
+        )
+    }
+
+    #[inline(always)]
+    fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write_data_generic(value, context, true)
+    }
+
+    #[inline(always)]
+    fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read_data(context)
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            field_ref_mode(remote_field_type),
+            field_read_type_info::<T>(context, remote_field_type),
+        )
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &T,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        _has_generics: bool,
+    ) -> Result<(), Error> {
+        T::fory_write(value, context, ref_mode, write_type_info, true)
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<T, Error> {
+        T::fory_read(context, ref_mode, read_type_info)
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<T, Error> {
+        T::fory_read_with_type_info(context, ref_mode, type_info)
+    }
+
+    #[inline(always)]
+    fn default_value() -> T {
+        T::fory_default()
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        context.writer.write_u8(TYPE_ID);
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        let remote = context.reader.read_u8()?;
+        if remote != TYPE_ID {
+            return Err(Error::type_mismatch(TYPE_ID as u32, remote as u32));
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        TypeId::try_from(TYPE_ID).unwrap_or(TypeId::UNKNOWN)
+    }
+}
+
+pub struct MapSerializerCodec<T, K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool>(
+    PhantomData<(T, K, V, KC, VC)>,
+);
+
+impl<T, K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+    for MapSerializerCodec<T, K, V, KC, VC, NULLABLE, TRACK_REF>
+where
+    T: Serializer + ForyDefault,
+    K: 'static,
+    V: 'static,
+    KC: Codec<K>,
+    VC: Codec<V>,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        Ok(FieldType::new_with_ref(
+            TypeId::MAP as u32,
+            NULLABLE,
+            TRACK_REF,
+            vec![
+                KC::field_type(type_resolver)?,
+                VC::field_type(type_resolver)?,
+            ],
+        ))
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+    }
+
+    #[inline(always)]
+    fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write(
+            value,
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            field_write_type_info::<T>(context),
+            true,
+        )
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+            serializer_read_type_info::<T>(context),
+        )
+    }
+
+    #[inline(always)]
+    fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+        T::fory_write_data_generic(value, context, true)
+    }
+
+    #[inline(always)]
+    fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+        T::fory_read_data(context)
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<T, Error> {
+        T::fory_read(
+            context,
+            field_ref_mode(remote_field_type),
+            field_read_type_info::<T>(context, remote_field_type),
+        )
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &T,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        _has_generics: bool,
+    ) -> Result<(), Error> {
+        T::fory_write(value, context, ref_mode, write_type_info, true)
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<T, Error> {
+        T::fory_read(context, ref_mode, read_type_info)
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<T, Error> {
+        T::fory_read_with_type_info(context, ref_mode, type_info)
+    }
+
+    #[inline(always)]
+    fn default_value() -> T {
+        T::fory_default()
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        context.writer.write_u8(TypeId::MAP as u8);
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        let remote = context.reader.read_u8()? as u32;
+        if remote != TypeId::MAP as u32 {
+            return Err(Error::type_mismatch(TypeId::MAP as u32, remote));
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        TypeId::MAP
+    }
+}
+
+pub struct OptionCodec<T, C, const TRACK_REF: bool>(PhantomData<(T, C)>);
+
+impl<T, C, const TRACK_REF: bool> Codec<Option<T>> for OptionCodec<T, C, TRACK_REF>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        let mut field_type = C::field_type(type_resolver)?;
+        field_type.nullable = true;
+        field_type.track_ref = TRACK_REF;
+        Ok(field_type)
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        C::reserved_space() + 1
+    }
+
+    #[inline(always)]
+    fn write_field(value: &Option<T>, context: &mut WriteContext) -> Result<(), Error> {
+        Self::write_with_mode(
+            value,
+            context,
+            if TRACK_REF {
+                RefMode::Tracking
+            } else {
+                RefMode::NullOnly
+            },
+            codec_write_type_info::<T, C>(context),
+            false,
+        )
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<Option<T>, Error> {
+        Self::read_with_mode(
+            context,
+            if TRACK_REF {
+                RefMode::Tracking
+            } else {
+                RefMode::NullOnly
+            },
+            codec_read_type_info_static::<T, C>(context),
+        )
+    }
+
+    #[inline(always)]
+    fn write_data(value: &Option<T>, context: &mut WriteContext) -> Result<(), Error> {
+        let value = value.as_ref().ok_or_else(|| {
+            Error::invalid_data("Option::None cannot be written as non-null data")
+        })?;
+        C::write_data(value, context)
+    }
+
+    #[inline(always)]
+    fn read_data(context: &mut ReadContext) -> Result<Option<T>, Error> {
+        Ok(Some(C::read_data(context)?))
+    }
+
+    #[inline(always)]
+    fn read_data_with_type(
+        context: &mut ReadContext,
+        remote_data_type: &FieldType,
+    ) -> Result<Option<T>, Error> {
+        Ok(Some(C::read_data_with_type(context, remote_data_type)?))
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<Option<T>, Error> {
+        let ref_mode = field_ref_mode(remote_field_type);
+        let read_type_info = codec_read_type_info::<T, C>(context, remote_field_type);
+        if ref_mode != RefMode::None {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(None);
+            }
+            if ref_mode == RefMode::Tracking {
+                context.reader.move_back(1);
+                return Ok(Some(C::read_with_mode(
+                    context,
+                    RefMode::Tracking,
+                    read_type_info,
+                )?));
+            }
+        }
+        if read_type_info || C::is_polymorphic() || C::is_shared_ref() {
+            Ok(Some(C::read_with_mode(
+                context,
+                RefMode::None,
+                read_type_info,
+            )?))
+        } else {
+            Ok(Some(C::read_data_with_type(context, remote_field_type)?))
+        }
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &Option<T>,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        has_generics: bool,
+    ) -> Result<(), Error> {
+        match ref_mode {
+            RefMode::None => Self::write_data(value, context),
+            RefMode::NullOnly => {
+                if let Some(value) = value {
+                    context.writer.write_i8(RefFlag::NotNullValue as i8);
+                    C::write_with_mode(value, context, RefMode::None, write_type_info, has_generics)
+                } else {
+                    context.writer.write_i8(RefFlag::Null as i8);
+                    Ok(())
+                }
+            }
+            RefMode::Tracking => {
+                if let Some(value) = value {
+                    C::write_with_mode(
+                        value,
+                        context,
+                        RefMode::Tracking,
+                        write_type_info,
+                        has_generics,
+                    )
+                } else {
+                    context.writer.write_i8(RefFlag::Null as i8);
+                    Ok(())
+                }
+            }
+        }
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<Option<T>, Error> {
+        match ref_mode {
+            RefMode::None => Ok(Some(C::read_with_mode(
+                context,
+                RefMode::None,
+                read_type_info,
+            )?)),
+            RefMode::NullOnly => {
+                let ref_flag = context.reader.read_i8()?;
+                if ref_flag == RefFlag::Null as i8 {
+                    return Ok(None);
+                }
+                Ok(Some(C::read_with_mode(
+                    context,
+                    RefMode::None,
+                    read_type_info,
+                )?))
+            }
+            RefMode::Tracking => {
+                let ref_flag = context.reader.read_i8()?;
+                if ref_flag == RefFlag::Null as i8 {
+                    return Ok(None);
+                }
+                context.reader.move_back(1);
+                Ok(Some(C::read_with_mode(
+                    context,
+                    RefMode::Tracking,
+                    read_type_info,
+                )?))
+            }
+        }
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<Option<T>, Error> {
+        match ref_mode {
+            RefMode::None => Ok(Some(C::read_with_type_info(
+                context,
+                RefMode::None,
+                type_info,
+            )?)),
+            RefMode::NullOnly => {
+                let ref_flag = context.reader.read_i8()?;
+                if ref_flag == RefFlag::Null as i8 {
+                    return Ok(None);
+                }
+                Ok(Some(C::read_with_type_info(
+                    context,
+                    RefMode::None,
+                    type_info,
+                )?))
+            }
+            RefMode::Tracking => {
+                let ref_flag = context.reader.read_i8()?;
+                if ref_flag == RefFlag::Null as i8 {
+                    return Ok(None);
+                }
+                context.reader.move_back(1);
+                Ok(Some(C::read_with_type_info(
+                    context,
+                    RefMode::Tracking,
+                    type_info,
+                )?))
+            }
+        }
+    }
+
+    #[inline(always)]
+    fn default_value() -> Option<T> {
+        None
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        C::write_type_info(context)
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        C::read_type_info(context)
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        C::static_type_id()
+    }
+
+    #[inline(always)]
+    fn is_option() -> bool {
+        true
+    }
+
+    #[inline(always)]
+    fn is_none(value: &Option<T>) -> bool {
+        value.is_none()
+    }
+
+    #[inline(always)]
+    fn is_polymorphic() -> bool {
+        C::is_polymorphic()
+    }
+
+    #[inline(always)]
+    fn is_shared_ref() -> bool {
+        C::is_shared_ref()
+    }
+}
+
+macro_rules! signed_int_codec {
+    ($name:ident, $ty:ty, $default_type:expr, $fixed_type:expr, $tagged_type:expr, $write_fixed:ident, $read_fixed:ident, $write_var:ident, $read_var:ident, $write_tagged:ident, $read_tagged:ident) => {
+        pub struct $name<const WIRE_TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool>;
+
+        impl<const WIRE_TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool> Codec<$ty>
+            for $name<WIRE_TYPE_ID, NULLABLE, TRACK_REF>
+        {
+            #[inline(always)]
+            fn field_type(_: &TypeResolver) -> Result<FieldType, Error> {
+                Ok(FieldType::new_with_ref(
+                    WIRE_TYPE_ID as u32,
+                    NULLABLE,
+                    TRACK_REF,
+                    Vec::new(),
+                ))
+            }
+
+            #[inline(always)]
+            fn reserved_space() -> usize {
+                std::mem::size_of::<$ty>() + 1
+            }
+
+            #[inline(always)]
+            fn write_field(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+                if NULLABLE {
+                    context.writer.write_i8(RefFlag::NotNullValue as i8);
+                }
+                Self::write_data(value, context)
+            }
+
+            #[inline(always)]
+            fn read_field(context: &mut ReadContext) -> Result<$ty, Error> {
+                if NULLABLE {
+                    let ref_flag = context.reader.read_i8()?;
+                    if ref_flag == RefFlag::Null as i8 {
+                        return Ok(<$ty as ForyDefault>::fory_default());
+                    }
+                }
+                Self::read_data(context)
+            }
+
+            #[inline(always)]
+            fn write_data(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+                match WIRE_TYPE_ID as u32 {
+                    x if x == $fixed_type => context.writer.$write_fixed(*value),
+                    x if x == $tagged_type => context.writer.$write_tagged(*value),
+                    _ => context.writer.$write_var(*value),
+                }
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn read_data(context: &mut ReadContext) -> Result<$ty, Error> {
+                match WIRE_TYPE_ID as u32 {
+                    x if x == $fixed_type => context.reader.$read_fixed(),
+                    x if x == $tagged_type => context.reader.$read_tagged(),
+                    _ => context.reader.$read_var(),
+                }
+            }
+
+            #[inline(always)]
+            fn read_data_with_type(
+                context: &mut ReadContext,
+                remote_data_type: &FieldType,
+            ) -> Result<$ty, Error> {
+                match remote_data_type.type_id {
+                    x if x == $fixed_type => context.reader.$read_fixed(),
+                    x if x == $tagged_type => context.reader.$read_tagged(),
+                    _ => context.reader.$read_var(),
+                }
+            }
+
+            #[inline(always)]
+            fn read_field_with_type(
+                context: &mut ReadContext,
+                remote_field_type: &FieldType,
+            ) -> Result<$ty, Error> {
+                if field_ref_mode(remote_field_type) != RefMode::None {
+                    let ref_flag = context.reader.read_i8()?;
+                    if ref_flag == RefFlag::Null as i8 {
+                        return Ok(<$ty as ForyDefault>::fory_default());
+                    }
+                }
+                match remote_field_type.type_id {
+                    x if x == $fixed_type => context.reader.$read_fixed(),
+                    x if x == $tagged_type => context.reader.$read_tagged(),
+                    _ => context.reader.$read_var(),
+                }
+            }
+
+            #[inline(always)]
+            fn write_with_mode(
+                value: &$ty,
+                context: &mut WriteContext,
+                ref_mode: RefMode,
+                write_type_info: bool,
+                _has_generics: bool,
+            ) -> Result<(), Error> {
+                if ref_mode != RefMode::None {
+                    context.writer.write_i8(RefFlag::NotNullValue as i8);
+                }
+                if write_type_info {
+                    Self::write_type_info(context)?;
+                }
+                Self::write_data(value, context)
+            }
+
+            #[inline(always)]
+            fn read_with_mode(
+                context: &mut ReadContext,
+                ref_mode: RefMode,
+                read_type_info: bool,
+            ) -> Result<$ty, Error> {
+                if ref_mode != RefMode::None {
+                    let ref_flag = context.reader.read_i8()?;
+                    if ref_flag == RefFlag::Null as i8 {
+                        return Ok(<$ty as ForyDefault>::fory_default());
+                    }
+                }
+                if read_type_info {
+                    let remote_field_type = Self::read_type_info_as_field_type(context)?;
+                    return Self::read_data_with_type(context, &remote_field_type);
+                }
+                Self::read_data(context)
+            }
+
+            #[inline(always)]
+            fn read_with_type_info(
+                context: &mut ReadContext,
+                ref_mode: RefMode,
+                _type_info: std::rc::Rc<crate::TypeInfo>,
+            ) -> Result<$ty, Error> {
+                Self::read_with_mode(context, ref_mode, false)
+            }
+
+            #[inline(always)]
+            fn default_value() -> $ty {
+                <$ty as ForyDefault>::fory_default()
+            }
+
+            #[inline(always)]
+            fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+                context.writer.write_var_u32(WIRE_TYPE_ID as u32);
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+                let remote = context.reader.read_var_u32()?;
+                if !same_numeric_family($default_type, remote) {
+                    return Err(Error::type_mismatch($default_type, remote));
+                }
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn read_type_info_as_field_type(context: &mut ReadContext) -> Result<FieldType, Error> {
+                let remote = context.reader.read_var_u32()?;
+                if !same_numeric_family($default_type, remote) {
+                    return Err(Error::type_mismatch($default_type, remote));
+                }
+                Ok(FieldType::new(remote, false, Vec::new()))
+            }
+
+            #[inline(always)]
+            fn static_type_id() -> TypeId {
+                TypeId::try_from(WIRE_TYPE_ID).unwrap_or(TypeId::UNKNOWN)
+            }
+        }
+    };
+}
+
+signed_int_codec!(
+    I32Codec,
+    i32,
+    type_id::VARINT32,
+    type_id::INT32,
+    UNKNOWN,
+    write_i32,
+    read_i32,
+    write_var_i32,
+    read_var_i32,
+    write_var_i32,
+    read_var_i32
+);
+signed_int_codec!(
+    I64Codec,
+    i64,
+    type_id::VARINT64,
+    type_id::INT64,
+    type_id::TAGGED_INT64,
+    write_i64,
+    read_i64,
+    write_var_i64,
+    read_var_i64,
+    write_tagged_i64,
+    read_tagged_i64
+);
+signed_int_codec!(
+    U32Codec,
+    u32,
+    type_id::VAR_UINT32,
+    type_id::UINT32,
+    UNKNOWN,
+    write_u32,
+    read_u32,
+    write_var_u32,
+    read_var_u32,
+    write_var_u32,
+    read_var_u32
+);
+signed_int_codec!(
+    U64Codec,
+    u64,
+    type_id::VAR_UINT64,
+    type_id::UINT64,
+    type_id::TAGGED_UINT64,
+    write_u64,
+    read_u64,
+    write_var_u64,
+    read_var_u64,
+    write_tagged_u64,
+    read_tagged_u64
+);
+
+pub struct VecCodec<T, C, const NULLABLE: bool, const TRACK_REF: bool>(PhantomData<(T, C)>);
+
+impl<T, C, const NULLABLE: bool, const TRACK_REF: bool> Codec<Vec<T>>
+    for VecCodec<T, C, NULLABLE, TRACK_REF>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        let element_type = C::field_type(type_resolver)?;
+        Ok(FieldType::new_with_ref(
+            TypeId::LIST as u32,
+            NULLABLE,
+            TRACK_REF,
+            vec![element_type],
+        ))
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        std::mem::size_of::<u32>() + SIZE_OF_REF_AND_TYPE
+    }
+
+    #[inline(always)]
+    fn write_field(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error> {
+        if NULLABLE {
+            context.writer.write_i8(RefFlag::NotNullValue as i8);
+        }
+        Self::write_data(value, context)
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<Vec<T>, Error> {
+        if NULLABLE {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(Vec::new());
+            }
+        }
+        Self::read_data(context)
+    }
+
+    fn write_data(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error> {
+        let len = value.len();
+        context.writer.write_var_u32(len as u32);
+        if len == 0 {
+            return Ok(());
+        }
+        if C::is_polymorphic() || C::is_shared_ref() {
+            return write_vec_dynamic::<T, C>(value, context);
+        }
+        let mut header = IS_SAME_TYPE;
+        let mut has_null = false;
+        if C::is_option() {
+            for item in value {
+                if C::is_none(item) {
+                    has_null = true;
+                    break;
+                }
+            }
+        }
+        if has_null {
+            header |= HAS_NULL;
+        }
+        if !need_to_write_type_for_field(C::static_type_id()) {
+            header |= DECL_ELEMENT_TYPE;
+            context.writer.write_u8(header);
+        } else {
+            context.writer.write_u8(header);
+            C::write_type_info(context)?;
+        }
+        context.writer.reserve(len * C::reserved_space());
+        if has_null {
+            for item in value {
+                if C::is_none(item) {
+                    context.writer.write_i8(RefFlag::Null as i8);
+                    continue;
+                }
+                context.writer.write_i8(RefFlag::NotNullValue as i8);
+                C::write_data(item, context)?;
+            }
+        } else {
+            for item in value {
+                C::write_data(item, context)?;
+            }
+        }
+        Ok(())
+    }
+
+    fn read_data(context: &mut ReadContext) -> Result<Vec<T>, Error> {
+        let len = context.reader.read_var_u32()?;
+        if len == 0 {
+            return Ok(Vec::new());
+        }
+        let max = context.max_collection_size();
+        if len > max {
+            return Err(Error::size_limit_exceeded(format!(
+                "Collection size {} exceeds limit {}",
+                len, max
+            )));
+        }
+        let header = context.reader.read_u8()?;
+        if C::is_polymorphic() || C::is_shared_ref() {
+            let field_type = Self::field_type(context.get_type_resolver())?;
+            return read_vec_dynamic_items::<T, C>(context, len, header, &field_type);
+        }
+        if (header & IS_SAME_TYPE) == 0 {
+            return Err(Error::type_error(
+                "Type inconsistent, target collection element type is not polymorphic",
+            ));
+        }
+        if (header & DECL_ELEMENT_TYPE) == 0 {
+            C::read_type_info(context)?;
+        }
+        let has_null = (header & HAS_NULL) != 0;
+        let mut vec = Vec::with_capacity(len as usize);
+        if has_null {
+            for _ in 0..len {
+                let flag = context.reader.read_i8()?;
+                if flag == RefFlag::Null as i8 {
+                    vec.push(C::default_value());
+                } else {
+                    vec.push(C::read_data(context)?);
+                }
+            }
+        } else {
+            for _ in 0..len {
+                vec.push(C::read_data(context)?);
+            }
+        }
+        Ok(vec)
+    }
+
+    fn read_data_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<Vec<T>, Error> {
+        let len = context.reader.read_var_u32()?;
+        if len == 0 {
+            return Ok(Vec::new());
+        }
+        let max = context.max_collection_size();
+        if len > max {
+            return Err(Error::size_limit_exceeded(format!(
+                "Collection size {} exceeds limit {}",
+                len, max
+            )));
+        }
+        let header = context.reader.read_u8()?;
+        let has_null = (header & HAS_NULL) != 0;
+        let is_same_type = (header & IS_SAME_TYPE) != 0;
+        let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+        if C::is_polymorphic() || C::is_shared_ref() {
+            return read_vec_dynamic_items::<T, C>(context, len, header, remote_field_type);
+        }
+        if !is_same_type {
+            return Err(Error::type_error(
+                "Type inconsistent, target collection element type is not polymorphic",
+            ));
+        }
+        let element_type = if is_declared {
+            generic_field_type(remote_field_type, 0, "list")?.clone()
+        } else {
+            C::read_type_info_as_field_type(context)?
+        };
+        let mut vec = Vec::with_capacity(len as usize);
+        if has_null {
+            for _ in 0..len {
+                let flag = context.reader.read_i8()?;
+                if flag == RefFlag::Null as i8 {
+                    vec.push(C::default_value());
+                } else {
+                    vec.push(C::read_data_with_type(context, &element_type)?);
+                }
+            }
+        } else {
+            for _ in 0..len {
+                vec.push(C::read_data_with_type(context, &element_type)?);
+            }
+        }
+        Ok(vec)
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<Vec<T>, Error> {
+        if field_ref_mode(remote_field_type) != RefMode::None {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(Vec::new());
+            }
+        }
+        Self::read_data_with_type(context, remote_field_type)
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &Vec<T>,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        _has_generics: bool,
+    ) -> Result<(), Error> {
+        if ref_mode != RefMode::None {
+            context.writer.write_i8(RefFlag::NotNullValue as i8);
+        }
+        if write_type_info {
+            Self::write_type_info(context)?;
+        }
+        Self::write_data(value, context)
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<Vec<T>, Error> {
+        if ref_mode != RefMode::None {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(Vec::new());
+            }
+        }
+        if read_type_info {
+            Self::read_type_info(context)?;
+        }
+        Self::read_data(context)
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        _type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<Vec<T>, Error> {
+        Self::read_with_mode(context, ref_mode, false)
+    }
+
+    #[inline(always)]
+    fn default_value() -> Vec<T> {
+        Vec::new()
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        context.writer.write_u8(TypeId::LIST as u8);
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        let remote = context.reader.read_u8()? as u32;
+        if remote != TypeId::LIST as u32 {
+            return Err(Error::type_mismatch(TypeId::LIST as u32, remote));
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        TypeId::LIST
+    }
+}
+
+fn write_vec_dynamic<T, C>(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    let mut has_null = false;
+    let mut is_same_type = true;
+    let mut first_type_id: Option<std::any::TypeId> = None;
+    for item in value {
+        if C::is_none(item) {
+            has_null = true;
+        } else if C::is_polymorphic() && is_same_type {
+            let concrete_id = C::concrete_type_id(item);
+            if let Some(first_id) = first_type_id {
+                if first_id != concrete_id {
+                    is_same_type = false;
+                }
+            } else {
+                first_type_id = Some(concrete_id);
+            }
+        }
+    }
+    if C::is_polymorphic() && is_same_type && first_type_id.is_none() {
+        is_same_type = false;
+    }
+    let mut header = 0u8;
+    if has_null {
+        header |= HAS_NULL;
+    }
+    if !need_to_write_type_for_field(C::static_type_id()) {
+        header |= DECL_ELEMENT_TYPE;
+    }
+    if is_same_type {
+        header |= IS_SAME_TYPE;
+    }
+    if C::is_shared_ref() {
+        header |= TRACKING_REF;
+    }
+    context.writer.write_u8(header);
+    if is_same_type && (header & DECL_ELEMENT_TYPE) == 0 {
+        if C::is_polymorphic() {
+            let type_id = first_type_id.ok_or_else(|| {
+                Error::type_error(
+                    "Unable to determine concrete type for polymorphic collection elements",
+                )
+            })?;
+            context.write_any_type_info(C::static_type_id() as u32, type_id)?;
+        } else {
+            C::write_type_info(context)?;
+        }
+    }
+    let elem_ref_mode = if C::is_shared_ref() {
+        RefMode::Tracking
+    } else if has_null {
+        RefMode::NullOnly
+    } else {
+        RefMode::None
+    };
+    if is_same_type {
+        for item in value {
+            if elem_ref_mode == RefMode::None {
+                C::write_data(item, context)?;
+            } else {
+                C::write_with_mode(item, context, elem_ref_mode, false, true)?;
+            }
+        }
+    } else {
+        for item in value {
+            C::write_with_mode(item, context, elem_ref_mode, true, true)?;
+        }
+    }
+    Ok(())
+}
+
+fn read_vec_dynamic_items<T, C>(
+    context: &mut ReadContext,
+    len: u32,
+    header: u8,
+    remote_field_type: &FieldType,
+) -> Result<Vec<T>, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    let is_track_ref = (header & TRACKING_REF) != 0;
+    let has_null = (header & HAS_NULL) != 0;
+    let is_same_type = (header & IS_SAME_TYPE) != 0;
+    let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+    let elem_ref_mode = if is_track_ref {
+        RefMode::Tracking
+    } else if has_null {
+        RefMode::NullOnly
+    } else {
+        RefMode::None
+    };
+    let mut vec = Vec::with_capacity(len as usize);
+    if is_same_type {
+        if C::is_polymorphic() {
+            if is_declared {
+                return Err(Error::invalid_data(
+                    "polymorphic collection element metadata must be written in the payload",
+                ));
+            }
+            let type_info = context.read_any_type_info()?;
+            for _ in 0..len {
+                vec.push(C::read_with_type_info(
+                    context,
+                    elem_ref_mode,
+                    type_info.clone(),
+                )?);
+            }
+        } else {
+            let element_type = if is_declared {
+                generic_field_type(remote_field_type, 0, "list")?.clone()
+            } else {
+                C::read_type_info_as_field_type(context)?
+            };
+            for _ in 0..len {
+                if C::is_shared_ref() {
+                    vec.push(C::read_with_mode(context, elem_ref_mode, false)?);
+                } else {
+                    vec.push(C::read_data_with_type(context, &element_type)?);
+                }
+            }
+        }
+    } else {
+        for _ in 0..len {
+            vec.push(C::read_with_mode(context, elem_ref_mode, true)?);
+        }
+    }
+    Ok(vec)
+}
+
+pub struct HashMapCodec<K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool>(
+    PhantomData<(K, V, KC, VC)>,
+);
+
+impl<K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool> Codec<HashMap<K, V>>
+    for HashMapCodec<K, V, KC, VC, NULLABLE, TRACK_REF>
+where
+    K: Eq + Hash + 'static,
+    V: 'static,
+    KC: Codec<K>,
+    VC: Codec<V>,
+{
+    #[inline(always)]
+    fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+        Ok(FieldType::new_with_ref(
+            TypeId::MAP as u32,
+            NULLABLE,
+            TRACK_REF,
+            vec![
+                KC::field_type(type_resolver)?,
+                VC::field_type(type_resolver)?,
+            ],
+        ))
+    }
+
+    #[inline(always)]
+    fn reserved_space() -> usize {
+        std::mem::size_of::<u32>() + SIZE_OF_REF_AND_TYPE
+    }
+
+    #[inline(always)]
+    fn write_field(value: &HashMap<K, V>, context: &mut WriteContext) -> Result<(), Error> {
+        if NULLABLE {
+            context.writer.write_i8(RefFlag::NotNullValue as i8);
+        }
+        Self::write_data(value, context)
+    }
+
+    #[inline(always)]
+    fn read_field(context: &mut ReadContext) -> Result<HashMap<K, V>, Error> {
+        if NULLABLE {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(HashMap::new());
+            }
+        }
+        Self::read_data(context)
+    }
+
+    fn write_data(value: &HashMap<K, V>, context: &mut WriteContext) -> Result<(), Error> {
+        let len = value.len();
+        context.writer.write_var_u32(len as u32);
+        if len == 0 {
+            return Ok(());
+        }
+        if KC::is_polymorphic()
+            || KC::is_shared_ref()
+            || VC::is_polymorphic()
+            || VC::is_shared_ref()
+        {
+            return write_map_dynamic::<K, V, KC, VC>(value, context);
+        }
+        let key_declared = !need_to_write_type_for_field(KC::static_type_id());
+        let value_declared = !need_to_write_type_for_field(VC::static_type_id());
+        let mut header_offset = 0;
+        let mut pair_counter: u8 = 0;
+        let mut need_write_header = true;
+
+        for (key, value) in value {
+            let key_is_none = KC::is_none(key);
+            let value_is_none = VC::is_none(value);
+            if key_is_none || value_is_none {
+                if !need_write_header && pair_counter > 0 {
+                    context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+                    pair_counter = 0;
+                    need_write_header = true;
+                }
+                if key_is_none && value_is_none {
+                    context.writer.write_u8(KEY_NULL | VALUE_NULL);
+                    continue;
+                }
+                if value_is_none {
+                    let mut header = VALUE_NULL;
+                    if KC::is_shared_ref() {
+                        header |= TRACKING_KEY_REF;
+                    }
+                    if key_declared && !KC::is_polymorphic() {
+                        header |= DECL_KEY_TYPE;
+                        context.writer.write_u8(header);
+                    } else {
+                        context.writer.write_u8(header);
+                        write_map_entry_type::<K, KC>(key, context)?;
+                    }
+                    write_map_entry_data::<K, KC>(key, context, KC::is_shared_ref())?;
+                    continue;
+                }
+                let mut header = KEY_NULL;
+                if VC::is_shared_ref() {
+                    header |= TRACKING_VALUE_REF;
+                }
+                if value_declared && !VC::is_polymorphic() {
+                    header |= DECL_VALUE_TYPE;
+                    context.writer.write_u8(header);
+                } else {
+                    context.writer.write_u8(header);
+                    write_map_entry_type::<V, VC>(value, context)?;
+                }
+                write_map_entry_data::<V, VC>(value, context, VC::is_shared_ref())?;
+                continue;
+            }
+
+            if need_write_header {
+                header_offset = context.writer.len();
+                context.writer.write_i16(-1);
+                let mut header = 0u8;
+                if KC::is_shared_ref() {
+                    header |= TRACKING_KEY_REF;
+                }
+                if VC::is_shared_ref() {
+                    header |= TRACKING_VALUE_REF;
+                }
+                if key_declared && !KC::is_polymorphic() {
+                    header |= DECL_KEY_TYPE;
+                } else {
+                    write_map_entry_type::<K, KC>(key, context)?;
+                }
+                if value_declared && !VC::is_polymorphic() {
+                    header |= DECL_VALUE_TYPE;
+                } else {
+                    write_map_entry_type::<V, VC>(value, context)?;
+                }
+                context.writer.set_bytes(header_offset, &[header]);
+                need_write_header = false;
+            }
+            write_map_entry_data::<K, KC>(key, context, KC::is_shared_ref())?;
+            write_map_entry_data::<V, VC>(value, context, VC::is_shared_ref())?;
+            pair_counter += 1;
+            if pair_counter == MAX_CHUNK_SIZE {
+                context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+                pair_counter = 0;
+                need_write_header = true;
+            }
+        }
+        if pair_counter > 0 {
+            context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+        }
+        Ok(())
+    }
+
+    fn read_data(context: &mut ReadContext) -> Result<HashMap<K, V>, Error> {
+        let len = context.reader.read_var_u32()?;
+        if len == 0 {
+            return Ok(HashMap::new());
+        }
+        let max = context.max_collection_size();
+        if len > max {
+            return Err(Error::size_limit_exceeded(format!(
+                "Map size {} exceeds limit {}",
+                len, max
+            )));
+        }
+        if KC::is_polymorphic()
+            || KC::is_shared_ref()
+            || VC::is_polymorphic()
+            || VC::is_shared_ref()
+        {
+            let field_type = Self::field_type(context.get_type_resolver())?;
+            return read_map_dynamic::<K, V, KC, VC>(context, len, &field_type);
+        }
+        read_map_static::<K, V, KC, VC>(context, len)
+    }
+
+    fn read_data_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<HashMap<K, V>, Error> {
+        let len = context.reader.read_var_u32()?;
+        if len == 0 {
+            return Ok(HashMap::new());
+        }
+        let max = context.max_collection_size();
+        if len > max {
+            return Err(Error::size_limit_exceeded(format!(
+                "Map size {} exceeds limit {}",
+                len, max
+            )));
+        }
+        if KC::is_polymorphic()
+            || KC::is_shared_ref()
+            || VC::is_polymorphic()
+            || VC::is_shared_ref()
+        {
+            return read_map_dynamic::<K, V, KC, VC>(context, len, remote_field_type);
+        }
+        let mut map = HashMap::with_capacity(len as usize);
+        let mut len_counter = 0;
+        while len_counter < len {
+            let header = context.reader.read_u8()?;
+            if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+                map.insert(KC::default_value(), VC::default_value());
+                len_counter += 1;
+                continue;
+            }
+            let key_declared = (header & DECL_KEY_TYPE) != 0;
+            let value_declared = (header & DECL_VALUE_TYPE) != 0;
+            let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+            let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+            if header & KEY_NULL != 0 {
+                let value_type =
+                    map_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+                let value = read_map_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+                map.insert(KC::default_value(), value);
+                len_counter += 1;
+                continue;
+            }
+            if header & VALUE_NULL != 0 {
+                let key_type =
+                    map_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+                let key = read_map_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+                map.insert(key, VC::default_value());
+                len_counter += 1;
+                continue;
+            }
+            let chunk_size = context.reader.read_u8()?;
+            let key_type = map_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+            let value_type =
+                map_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+            let cur_len = len_counter + chunk_size as u32;
+            if cur_len > len {
+                return Err(Error::invalid_data(format!(
+                    "current length {} exceeds total length {}",
+                    cur_len, len
+                )));
+            }
+            for _ in 0..chunk_size {
+                let key = read_map_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+                let value = read_map_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+                map.insert(key, value);
+            }
+            len_counter = cur_len;
+        }
+        Ok(map)
+    }
+
+    #[inline(always)]
+    fn read_field_with_type(
+        context: &mut ReadContext,
+        remote_field_type: &FieldType,
+    ) -> Result<HashMap<K, V>, Error> {
+        if field_ref_mode(remote_field_type) != RefMode::None {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(HashMap::new());
+            }
+        }
+        Self::read_data_with_type(context, remote_field_type)
+    }
+
+    #[inline(always)]
+    fn write_with_mode(
+        value: &HashMap<K, V>,
+        context: &mut WriteContext,
+        ref_mode: RefMode,
+        write_type_info: bool,
+        _has_generics: bool,
+    ) -> Result<(), Error> {
+        if ref_mode != RefMode::None {
+            context.writer.write_i8(RefFlag::NotNullValue as i8);
+        }
+        if write_type_info {
+            Self::write_type_info(context)?;
+        }
+        Self::write_data(value, context)
+    }
+
+    #[inline(always)]
+    fn read_with_mode(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        read_type_info: bool,
+    ) -> Result<HashMap<K, V>, Error> {
+        if ref_mode != RefMode::None {
+            let ref_flag = context.reader.read_i8()?;
+            if ref_flag == RefFlag::Null as i8 {
+                return Ok(HashMap::new());
+            }
+        }
+        if read_type_info {
+            Self::read_type_info(context)?;
+        }
+        Self::read_data(context)
+    }
+
+    #[inline(always)]
+    fn read_with_type_info(
+        context: &mut ReadContext,
+        ref_mode: RefMode,
+        _type_info: std::rc::Rc<crate::TypeInfo>,
+    ) -> Result<HashMap<K, V>, Error> {
+        Self::read_with_mode(context, ref_mode, false)
+    }
+
+    #[inline(always)]
+    fn default_value() -> HashMap<K, V> {
+        HashMap::new()
+    }
+
+    #[inline(always)]
+    fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        context.writer.write_u8(TypeId::MAP as u8);
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        let remote = context.reader.read_u8()? as u32;
+        if remote != TypeId::MAP as u32 {
+            return Err(Error::type_mismatch(TypeId::MAP as u32, remote));
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn static_type_id() -> TypeId {
+        TypeId::MAP
+    }
+}
+
+struct MapEntryReadType {
+    field_type: Option<FieldType>,
+    type_info: Option<std::rc::Rc<crate::TypeInfo>>,
+}
+
+fn read_map_static<K, V, KC, VC>(
+    context: &mut ReadContext,
+    len: u32,
+) -> Result<HashMap<K, V>, Error>
+where
+    K: Eq + Hash + 'static,
+    V: 'static,
+    KC: Codec<K>,
+    VC: Codec<V>,
+{
+    let mut map = HashMap::with_capacity(len as usize);
+    let mut len_counter = 0u32;
+    while len_counter < len {
+        let header = context.reader.read_u8()?;
+        if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+            map.insert(KC::default_value(), VC::default_value());
+            len_counter += 1;
+            continue;
+        }
+        let key_declared = (header & DECL_KEY_TYPE) != 0;
+        let value_declared = (header & DECL_VALUE_TYPE) != 0;
+        let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+        let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+
+        if header & KEY_NULL != 0 {
+            let value_type =
+                read_map_static_entry_type::<V, VC>(context, value_declared, track_value_ref)?;
+            let value =
+                read_map_static_entry_data::<V, VC>(context, value_type.as_ref(), track_value_ref)?;
+            map.insert(KC::default_value(), value);
+            len_counter += 1;
+            continue;
+        }
+        if header & VALUE_NULL != 0 {
+            let key_type =
+                read_map_static_entry_type::<K, KC>(context, key_declared, track_key_ref)?;
+            let key =
+                read_map_static_entry_data::<K, KC>(context, key_type.as_ref(), track_key_ref)?;
+            map.insert(key, VC::default_value());
+            len_counter += 1;
+            continue;
+        }
+
+        let chunk_size = context.reader.read_u8()?;
+        let key_type = read_map_static_entry_type::<K, KC>(context, key_declared, track_key_ref)?;
+        let value_type =
+            read_map_static_entry_type::<V, VC>(context, value_declared, track_value_ref)?;
+        let cur_len = len_counter + chunk_size as u32;
+        if cur_len > len {
+            return Err(Error::invalid_data(format!(
+                "current length {} exceeds total length {}",
+                cur_len, len
+            )));
+        }
+        for _ in 0..chunk_size {
+            let key =
+                read_map_static_entry_data::<K, KC>(context, key_type.as_ref(), track_key_ref)?;
+            let value =
+                read_map_static_entry_data::<V, VC>(context, value_type.as_ref(), track_value_ref)?;
+            map.insert(key, value);
+        }
+        len_counter = cur_len;
+    }
+    Ok(map)
+}
+
+#[inline(always)]
+fn read_map_static_entry_type<T, C>(
+    context: &mut ReadContext,
+    declared: bool,
+    track_ref: bool,
+) -> Result<Option<FieldType>, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if declared {
+        Ok(None)
+    } else if track_ref {
+        C::read_type_info(context)?;
+        Ok(None)
+    } else {
+        C::read_type_info_as_field_type(context).map(Some)
+    }
+}
+
+#[inline(always)]
+fn read_map_static_entry_data<T, C>(
+    context: &mut ReadContext,
+    entry_type: Option<&FieldType>,
+    track_ref: bool,
+) -> Result<T, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if track_ref {
+        C::read_with_mode(context, RefMode::Tracking, false)
+    } else if let Some(entry_type) = entry_type {
+        C::read_data_with_type(context, entry_type)
+    } else {
+        C::read_data(context)
+    }
+}
+
+fn read_map_dynamic<K, V, KC, VC>(
+    context: &mut ReadContext,
+    len: u32,
+    remote_field_type: &FieldType,
+) -> Result<HashMap<K, V>, Error>
+where
+    K: Eq + Hash + 'static,
+    V: 'static,
+    KC: Codec<K>,
+    VC: Codec<V>,
+{
+    let mut map = HashMap::with_capacity(len as usize);
+    let mut len_counter = 0u32;
+    while len_counter < len {
+        let header = context.reader.read_u8()?;
+        if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+            map.insert(KC::default_value(), VC::default_value());
+            len_counter += 1;
+            continue;
+        }
+        let key_declared = (header & DECL_KEY_TYPE) != 0;
+        let value_declared = (header & DECL_VALUE_TYPE) != 0;
+        let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+        let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+
+        if header & KEY_NULL != 0 {
+            let value_type = read_map_dynamic_entry_type::<V, VC>(
+                context,
+                value_declared,
+                remote_field_type,
+                1,
+            )?;
+            let value =
+                read_map_dynamic_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+            map.insert(KC::default_value(), value);
+            len_counter += 1;
+            continue;
+        }
+        if header & VALUE_NULL != 0 {
+            let key_type =
+                read_map_dynamic_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+            let key = read_map_dynamic_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+            map.insert(key, VC::default_value());
+            len_counter += 1;
+            continue;
+        }
+
+        let chunk_size = context.reader.read_u8()?;
+        let key_type =
+            read_map_dynamic_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+        let value_type =
+            read_map_dynamic_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+        let cur_len = len_counter + chunk_size as u32;
+        if cur_len > len {
+            return Err(Error::invalid_data(format!(
+                "current length {} exceeds total length {}",
+                cur_len, len
+            )));
+        }
+        for _ in 0..chunk_size {
+            let key = read_map_dynamic_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+            let value =
+                read_map_dynamic_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+            map.insert(key, value);
+        }
+        len_counter = cur_len;
+    }
+    Ok(map)
+}
+
+#[inline(always)]
+fn read_map_dynamic_entry_type<T, C>(
+    context: &mut ReadContext,
+    declared: bool,
+    remote_field_type: &FieldType,
+    generic_index: usize,
+) -> Result<MapEntryReadType, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if declared {
+        return Ok(MapEntryReadType {
+            field_type: Some(generic_field_type(remote_field_type, generic_index, "map")?.clone()),
+            type_info: None,
+        });
+    }
+    if C::is_polymorphic() {
+        Ok(MapEntryReadType {
+            field_type: None,
+            type_info: Some(context.read_any_type_info()?),
+        })
+    } else {
+        Ok(MapEntryReadType {
+            field_type: Some(C::read_type_info_as_field_type(context)?),
+            type_info: None,
+        })
+    }
+}
+
+#[inline(always)]
+fn read_map_dynamic_entry_data<T, C>(
+    context: &mut ReadContext,
+    read_type: &MapEntryReadType,
+    track_ref: bool,
+) -> Result<T, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    let ref_mode = if track_ref {
+        RefMode::Tracking
+    } else {
+        RefMode::None
+    };
+    if let Some(type_info) = read_type.type_info.as_ref() {
+        return C::read_with_type_info(context, ref_mode, type_info.clone());
+    }
+    if track_ref {
+        return C::read_with_mode(context, ref_mode, false);
+    }
+    let field_type = read_type
+        .field_type
+        .as_ref()
+        .ok_or_else(|| Error::invalid_data("map entry field metadata is missing"))?;
+    C::read_data_with_type(context, field_type)
+}
+
+fn write_map_dynamic<K, V, KC, VC>(
+    value: &HashMap<K, V>,
+    context: &mut WriteContext,
+) -> Result<(), Error>
+where
+    K: Eq + Hash + 'static,
+    V: 'static,
+    KC: Codec<K>,
+    VC: Codec<V>,
+{
+    let key_declared = !need_to_write_type_for_field(KC::static_type_id());
+    let value_declared = !need_to_write_type_for_field(VC::static_type_id());
+    let key_is_polymorphic = KC::is_polymorphic();
+    let value_is_polymorphic = VC::is_polymorphic();
+    let key_is_shared_ref = KC::is_shared_ref();
+    let value_is_shared_ref = VC::is_shared_ref();
+    let mut current_key_type_id: Option<std::any::TypeId> = None;
+    let mut current_value_type_id: Option<std::any::TypeId> = None;
+    let mut header_offset = 0;
+    let mut pair_counter: u8 = 0;
+    let mut need_write_header = true;
+
+    for (key, value) in value {
+        let key_is_none = KC::is_none(key);
+        let value_is_none = VC::is_none(value);
+        if key_is_none || value_is_none {
+            if pair_counter > 0 {
+                context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+                pair_counter = 0;
+                need_write_header = true;
+            }
+            if key_is_none && value_is_none {
+                context.writer.write_u8(KEY_NULL | VALUE_NULL);
+                continue;
+            }
+            if value_is_none {
+                let mut header = VALUE_NULL;
+                if key_is_shared_ref {
+                    header |= TRACKING_KEY_REF;
+                }
+                if key_declared && !key_is_polymorphic {
+                    header |= DECL_KEY_TYPE;
+                    context.writer.write_u8(header);
+                } else {
+                    context.writer.write_u8(header);
+                    write_map_entry_type::<K, KC>(key, context)?;
+                }
+                write_map_entry_data::<K, KC>(key, context, key_is_shared_ref)?;
+                continue;
+            }
+            let mut header = KEY_NULL;
+            if value_is_shared_ref {
+                header |= TRACKING_VALUE_REF;
+            }
+            if value_declared && !value_is_polymorphic {
+                header |= DECL_VALUE_TYPE;
+                context.writer.write_u8(header);
+            } else {
+                context.writer.write_u8(header);
+                write_map_entry_type::<V, VC>(value, context)?;
+            }
+            write_map_entry_data::<V, VC>(value, context, value_is_shared_ref)?;
+            continue;
+        }
+
+        let key_type_id = if key_is_polymorphic {
+            Some(KC::concrete_type_id(key))
+        } else {
+            None
+        };
+        let value_type_id = if value_is_polymorphic {
+            Some(VC::concrete_type_id(value))
+        } else {
+            None
+        };
+        let types_changed = (key_is_polymorphic || value_is_polymorphic)
+            && (key_type_id != current_key_type_id || value_type_id != current_value_type_id);
+
+        if need_write_header || types_changed {
+            if pair_counter > 0 {
+                context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+                pair_counter = 0;
+            }
+            header_offset = context.writer.len();
+            context.writer.write_i16(-1);
+            let mut header = 0u8;
+            if key_is_shared_ref {
+                header |= TRACKING_KEY_REF;
+            }
+            if value_is_shared_ref {
+                header |= TRACKING_VALUE_REF;
+            }
+            if key_declared && !key_is_polymorphic {
+                header |= DECL_KEY_TYPE;
+            } else {
+                write_map_entry_type::<K, KC>(key, context)?;
+            }
+            if value_declared && !value_is_polymorphic {
+                header |= DECL_VALUE_TYPE;
+            } else {
+                write_map_entry_type::<V, VC>(value, context)?;
+            }
+            context.writer.set_bytes(header_offset, &[header]);
+            need_write_header = false;
+            current_key_type_id = key_type_id;
+            current_value_type_id = value_type_id;
+        }
+
+        write_map_entry_data::<K, KC>(key, context, key_is_shared_ref)?;
+        write_map_entry_data::<V, VC>(value, context, value_is_shared_ref)?;
+        pair_counter += 1;
+        if pair_counter == MAX_CHUNK_SIZE {
+            context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+            pair_counter = 0;
+            need_write_header = true;
+            current_key_type_id = None;
+            current_value_type_id = None;
+        }
+    }
+    if pair_counter > 0 {
+        context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+    }
+    Ok(())
+}
+
+#[inline(always)]
+fn write_map_entry_type<T, C>(value: &T, context: &mut WriteContext) -> Result<(), Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if C::is_polymorphic() {
+        context.write_any_type_info(C::static_type_id() as u32, C::concrete_type_id(value))?;
+        Ok(())
+    } else {
+        C::write_type_info(context)
+    }
+}
+
+#[inline(always)]
+fn write_map_entry_data<T, C>(
+    value: &T,
+    context: &mut WriteContext,
+    track_ref: bool,
+) -> Result<(), Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if track_ref {
+        C::write_with_mode(value, context, RefMode::Tracking, false, true)
+    } else {
+        C::write_data(value, context)
+    }
+}
+
+#[inline(always)]
+fn map_entry_type<T, C>(
+    context: &mut ReadContext,
+    declared: bool,
+    remote_field_type: &FieldType,
+    generic_index: usize,
+) -> Result<FieldType, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if declared {
+        Ok(generic_field_type(remote_field_type, generic_index, "map")?.clone())
+    } else {
+        C::read_type_info_as_field_type(context)
+    }
+}
+
+#[inline(always)]
+fn read_map_entry_data<T, C>(
+    context: &mut ReadContext,
+    remote_entry_type: &FieldType,
+    track_ref: bool,
+) -> Result<T, Error>
+where
+    T: 'static,
+    C: Codec<T>,
+{
+    if track_ref {
+        C::read_with_mode(context, RefMode::Tracking, false)
+    } else {
+        C::read_data_with_type(context, remote_entry_type)
+    }
+}
+
+#[inline(always)]
+fn any_field_type<const NULLABLE: bool, const TRACK_REF: bool>() -> FieldType {
+    FieldType::new_with_ref(TypeId::UNKNOWN as u32, NULLABLE, TRACK_REF, Vec::new())
+}
+
+#[inline(always)]
+fn any_ref_mode<const NULLABLE: bool, const TRACK_REF: bool>() -> RefMode {
+    if TRACK_REF {
+        RefMode::Tracking
+    } else if NULLABLE {
+        RefMode::NullOnly
+    } else {
+        RefMode::None
+    }
+}
+
+macro_rules! any_codec {
+    ($name:ident, $ty:ty) => {
+        pub struct $name<const NULLABLE: bool, const TRACK_REF: bool>;
+
+        impl<const NULLABLE: bool, const TRACK_REF: bool> Codec<$ty>
+            for $name<NULLABLE, TRACK_REF>
+        {
+            #[inline(always)]
+            fn field_type(_: &TypeResolver) -> Result<FieldType, Error> {
+                Ok(any_field_type::<NULLABLE, TRACK_REF>())
+            }
+
+            #[inline(always)]
+            fn reserved_space() -> usize {
+                <$ty as Serializer>::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+            }
+
+            #[inline(always)]
+            fn write_field(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+                <$ty as Serializer>::fory_write(
+                    value,
+                    context,
+                    any_ref_mode::<NULLABLE, TRACK_REF>(),
+                    field_write_type_info::<$ty>(context),
+                    false,
+                )
+            }
+
+            #[inline(always)]
+            fn read_field(context: &mut ReadContext) -> Result<$ty, Error> {
+                <$ty as Serializer>::fory_read(
+                    context,
+                    any_ref_mode::<NULLABLE, TRACK_REF>(),
+                    codec_read_type_info_static::<$ty, Self>(context),
+                )
+            }
+
+            #[inline(always)]
+            fn write_data(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+                <$ty as Serializer>::fory_write_data_generic(value, context, false)
+            }
+
+            #[inline(always)]
+            fn read_data(context: &mut ReadContext) -> Result<$ty, Error> {
+                <$ty as Serializer>::fory_read_data(context)
+            }
+
+            #[inline(always)]
+            fn read_field_with_type(
+                context: &mut ReadContext,
+                remote_field_type: &FieldType,
+            ) -> Result<$ty, Error> {
+                <$ty as Serializer>::fory_read(
+                    context,
+                    field_ref_mode(remote_field_type),
+                    codec_read_type_info::<$ty, Self>(context, remote_field_type),
+                )
+            }
+
+            #[inline(always)]
+            fn write_with_mode(
+                value: &$ty,
+                context: &mut WriteContext,
+                ref_mode: RefMode,
+                write_type_info: bool,
+                has_generics: bool,
+            ) -> Result<(), Error> {
+                <$ty as Serializer>::fory_write(
+                    value,
+                    context,
+                    ref_mode,
+                    write_type_info,
+                    has_generics,
+                )
+            }
+
+            #[inline(always)]
+            fn read_with_mode(
+                context: &mut ReadContext,
+                ref_mode: RefMode,
+                read_type_info: bool,
+            ) -> Result<$ty, Error> {
+                <$ty as Serializer>::fory_read(context, ref_mode, read_type_info)
+            }
+
+            #[inline(always)]
+            fn read_with_type_info(
+                context: &mut ReadContext,
+                ref_mode: RefMode,
+                type_info: Rc<crate::TypeInfo>,
+            ) -> Result<$ty, Error> {
+                <$ty as Serializer>::fory_read_with_type_info(context, ref_mode, type_info)
+            }
+
+            #[inline(always)]
+            fn default_value() -> $ty {
+                <$ty as ForyDefault>::fory_default()
+            }
+
+            #[inline(always)]
+            fn write_type_info(_context: &mut WriteContext) -> Result<(), Error> {
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn read_type_info(_context: &mut ReadContext) -> Result<(), Error> {
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn static_type_id() -> TypeId {
+                TypeId::UNKNOWN
+            }
+
+            #[inline(always)]
+            fn is_polymorphic() -> bool {
+                true
+            }
+
+            #[inline(always)]
+            fn is_shared_ref() -> bool {
+                <$ty as Serializer>::fory_is_shared_ref()
+            }
+
+            #[inline(always)]
+            fn concrete_type_id(value: &$ty) -> std::any::TypeId {
+                <$ty as Serializer>::fory_concrete_type_id(value)
+            }
+        }
+    };
+}
+
+any_codec!(AnyBoxCodec, Box<dyn Any>);
+any_codec!(AnyRcCodec, Rc<dyn Any>);
+any_codec!(AnyArcCodec, Arc<dyn Any>);
diff --git a/rust/fory-core/src/serializer/core.rs b/rust/fory-core/src/serializer/core.rs
index 62bb62845..b91bc70b9 100644
--- a/rust/fory-core/src/serializer/core.rs
+++ b/rust/fory-core/src/serializer/core.rs
@@ -103,7 +103,7 @@ pub trait ForyDefault: Sized {
 ///
 /// `Serializer` is the primary trait that enables types to be serialized and deserialized
 /// using Fory's high-performance cross-language protocol. All types that can be serialized
-/// with Fory must implement this trait, either manually or via the `#[derive(ForyObject)]` macro.
+/// with Fory must implement this trait, either manually or via the `#[derive(ForyStruct)]` macro.
 ///
 /// # Architecture Overview
 ///
@@ -173,10 +173,10 @@ pub trait ForyDefault: Sized {
 ///
 /// # Derive Macro
 ///
-/// For struct types, you can use the `#[derive(ForyObject)]` macro instead of implementing manually:
+/// For struct types, you can use the `#[derive(ForyStruct)]` macro instead of implementing manually:
 ///
 /// ```rust,ignore
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
 /// struct Point {
 ///     x: f64,
 ///     y: f64,
@@ -1246,7 +1246,7 @@ pub trait Serializer: 'static {
 ///
 /// **User types with custom serialization should NOT implement this trait.** This trait is:
 ///
-/// - **Only for derive-based types**: Automatically implemented by `#[derive(ForyObject)]`
+/// - **Only for derive-based types**: Automatically implemented by `#[derive(ForyStruct)]`
 /// - **Internal infrastructure**: Used by Fory's struct/enum deserialization engine
 /// - **Not for manual implementation**: Unless you're extending Fory's core functionality
 ///
@@ -1264,10 +1264,10 @@ pub trait Serializer: 'static {
 ///
 /// # Automatic Implementation
 ///
-/// The `#[derive(ForyObject)]` macro automatically implements this trait for structs and enums:
+/// The `#[derive(ForyStruct)]` macro automatically implements this trait for structs and enums:
 ///
 /// ```rust,ignore
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
 /// struct Point {
 ///     x: f64,
 ///     y: f64,
@@ -1288,7 +1288,7 @@ pub trait Serializer: 'static {
 /// # Implementation Notes
 ///
 /// - **Do not implement manually** for user types with custom serialization (EXT types)
-/// - **Only via derive macro**: `#[derive(ForyObject)]` is the only supported way
+/// - **Only via derive macro**: `#[derive(ForyStruct)]` is the only supported way
 /// - **Internal use only**: Methods are called by Fory's serialization engine
 /// - **Schema evolution**: Enables adding/removing fields without breaking compatibility
 ///
@@ -1314,7 +1314,7 @@ pub trait StructSerializer: Serializer + 'static {
     ///
     /// # Implementation Notes
     ///
-    /// - Implemented automatically by `#[derive(ForyObject)]` macro
+    /// - Implemented automatically by `#[derive(ForyStruct)]` macro
     /// - Default returns empty vector (no fields)
     /// - **Do not implement** for user types with custom serialization (EXT types)
     #[allow(unused_variables)]
@@ -1339,7 +1339,7 @@ pub trait StructSerializer: Serializer + 'static {
     ///
     /// # Implementation Notes
     ///
-    /// - Implemented automatically by `#[derive(ForyObject)]` macro
+    /// - Implemented automatically by `#[derive(ForyStruct)]` macro
     /// - Used for efficient type resolution
     /// - **Do not implement** for user types with custom serialization (EXT types)
     fn fory_type_index() -> u32 {
@@ -1389,7 +1389,7 @@ pub trait StructSerializer: Serializer + 'static {
     ///
     /// # Implementation Notes
     ///
-    /// - Implemented automatically by `#[derive(ForyObject)]` macro
+    /// - Implemented automatically by `#[derive(ForyStruct)]` macro
     /// - Used for field ordering and compatibility
     /// - Field order affects wire format
     /// - **Do not implement** for user types with custom serialization (EXT types)
@@ -1426,7 +1426,7 @@ pub trait StructSerializer: Serializer + 'static {
     ///
     /// # Implementation Notes
     ///
-    /// - Implemented automatically by `#[derive(ForyObject)]` macro
+    /// - Implemented automatically by `#[derive(ForyStruct)]` macro
     /// - Enables forward and backward compatibility
     /// - Uses field names for matching when possible
     /// - Falls back to field order for unnamed fields
diff --git a/rust/fory-core/src/serializer/heap.rs b/rust/fory-core/src/serializer/heap.rs
index cfbd7be30..7203c9545 100644
--- a/rust/fory-core/src/serializer/heap.rs
+++ b/rust/fory-core/src/serializer/heap.rs
@@ -34,6 +34,14 @@ impl<T: Serializer + ForyDefault + Ord> Serializer for BinaryHeap<T> {
         write_collection_data(self, context, false)
     }
 
+    fn fory_write_data_generic(
+        &self,
+        context: &mut WriteContext,
+        has_generics: bool,
+    ) -> Result<(), Error> {
+        write_collection_data(self, context, has_generics)
+    }
+
     fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
         write_collection_type_info(context, TypeId::SET as u32)
     }
diff --git a/rust/fory-core/src/serializer/mod.rs b/rust/fory-core/src/serializer/mod.rs
index 1b2c97103..a51f48332 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -20,6 +20,8 @@ mod arc;
 mod array;
 mod bool;
 mod box_;
+#[doc(hidden)]
+pub mod codec;
 pub mod collection;
 mod datetime;
 pub mod enum_;
diff --git a/rust/fory-core/src/serializer/mutex.rs b/rust/fory-core/src/serializer/mutex.rs
index 9af0b06a2..90b69bcff 100644
--- a/rust/fory-core/src/serializer/mutex.rs
+++ b/rust/fory-core/src/serializer/mutex.rs
@@ -139,10 +139,7 @@ impl<T: Serializer + ForyDefault> Serializer for Mutex<T> {
 
     #[inline(always)]
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     #[inline(always)]
diff --git a/rust/fory-core/src/serializer/rc.rs b/rust/fory-core/src/serializer/rc.rs
index a42e6dbeb..7c0f46861 100644
--- a/rust/fory-core/src/serializer/rc.rs
+++ b/rust/fory-core/src/serializer/rc.rs
@@ -134,10 +134,7 @@ impl<T: Serializer + ForyDefault + 'static> Serializer for Rc<T> {
     }
 
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/serializer/refcell.rs b/rust/fory-core/src/serializer/refcell.rs
index da47ad678..3443b857f 100644
--- a/rust/fory-core/src/serializer/refcell.rs
+++ b/rust/fory-core/src/serializer/refcell.rs
@@ -137,10 +137,7 @@ impl<T: Serializer + ForyDefault> Serializer for RefCell<T> {
 
     #[inline(always)]
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     #[inline(always)]
diff --git a/rust/fory-core/src/serializer/skip.rs b/rust/fory-core/src/serializer/skip.rs
index 9e139b0e2..c905c227a 100644
--- a/rust/fory-core/src/serializer/skip.rs
+++ b/rust/fory-core/src/serializer/skip.rs
@@ -37,13 +37,10 @@ pub fn skip_field_value(
     skip_value(context, field_type, read_ref_flag, true, &None)
 }
 
-const UNKNOWN_FIELD_TYPE: FieldType = FieldType {
-    type_id: types::UNKNOWN,
-    user_type_id: u32::MAX,
-    nullable: true,
-    track_ref: false,
-    generics: vec![],
-};
+#[inline(always)]
+fn unknown_field_type() -> FieldType {
+    FieldType::new(types::UNKNOWN, true, Vec::new())
+}
 
 pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<(), Error> {
     // Handle ref flag first if needed
@@ -79,52 +76,26 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
     // This is critical for polymorphic collections where elements are struct types.
     let (field_type, type_info_opt) = match internal_id {
         types::LIST | types::SET => (
-            FieldType {
-                type_id,
-                user_type_id: u32::MAX,
-                nullable: true,
-                track_ref: false,
-                generics: vec![UNKNOWN_FIELD_TYPE],
-            },
+            FieldType::new(type_id, true, vec![unknown_field_type()]),
             None,
         ),
         types::MAP => (
-            FieldType {
+            FieldType::new(
                 type_id,
-                user_type_id: u32::MAX,
-                nullable: true,
-                track_ref: false,
-                generics: vec![UNKNOWN_FIELD_TYPE, UNKNOWN_FIELD_TYPE],
-            },
+                true,
+                vec![unknown_field_type(), unknown_field_type()],
+            ),
             None,
         ),
         types::COMPATIBLE_STRUCT | types::NAMED_COMPATIBLE_STRUCT => {
             // For compatible struct types, read type meta inline using streaming protocol
             let type_info = context.read_type_meta()?;
-            (
-                FieldType {
-                    type_id,
-                    user_type_id: u32::MAX,
-                    nullable: true,
-                    track_ref: false,
-                    generics: vec![],
-                },
-                Some(type_info),
-            )
+            (FieldType::new(type_id, true, Vec::new()), Some(type_info))
         }
         types::NAMED_ENUM | types::NAMED_EXT | types::NAMED_STRUCT | types::NAMED_UNION => {
             if context.is_share_meta() {
                 let type_info = context.read_type_meta()?;
-                (
-                    FieldType {
-                        type_id,
-                        user_type_id: u32::MAX,
-                        nullable: true,
-                        track_ref: false,
-                        generics: vec![],
-                    },
-                    Some(type_info),
-                )
+                (FieldType::new(type_id, true, Vec::new()), Some(type_info))
             } else {
                 let namespace = context.read_meta_string()?.to_owned();
                 let type_name = context.read_meta_string()?.to_owned();
@@ -134,16 +105,7 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
                     .get_type_resolver()
                     .get_type_info_by_meta_string_name(rc_namespace, rc_type_name)
                     .ok_or_else(|| crate::Error::type_error("Name harness not found"))?;
-                (
-                    FieldType {
-                        type_id,
-                        user_type_id: u32::MAX,
-                        nullable: true,
-                        track_ref: false,
-                        generics: vec![],
-                    },
-                    Some(type_info),
-                )
+                (FieldType::new(type_id, true, Vec::new()), Some(type_info))
             }
         }
         _ => {
@@ -155,13 +117,13 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
                 None
             };
             (
-                FieldType {
+                FieldType::new_with_user_type_id(
                     type_id,
-                    user_type_id: _user_type_id.unwrap_or(u32::MAX),
-                    nullable: true,
-                    track_ref: false,
-                    generics: vec![],
-                },
+                    _user_type_id.unwrap_or(u32::MAX),
+                    true,
+                    false,
+                    Vec::new(),
+                ),
                 type_info,
             )
         }
@@ -188,13 +150,13 @@ fn skip_collection(context: &mut ReadContext, field_type: &FieldType) -> Result<
     let (type_info, elem_field_type);
     let elem_type = if is_same_type && !is_declared {
         let type_info_rc = context.read_any_type_info()?;
-        elem_field_type = FieldType {
-            type_id: type_info_rc.get_type_id() as u32,
-            user_type_id: type_info_rc.get_user_type_id(),
-            nullable: has_null,
-            track_ref: false,
-            generics: vec![],
-        };
+        elem_field_type = FieldType::new_with_user_type_id(
+            type_info_rc.get_type_id() as u32,
+            type_info_rc.get_user_type_id(),
+            has_null,
+            false,
+            Vec::new(),
+        );
         type_info = Some(type_info_rc);
         &elem_field_type
     } else {
@@ -237,13 +199,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
             let (value_type_info, value_field_type);
             let value_type = if !value_declared {
                 let type_info = context.read_any_type_info()?;
-                value_field_type = FieldType {
-                    type_id: type_info.get_type_id() as u32,
-                    user_type_id: type_info.get_user_type_id(),
-                    nullable: true,
-                    track_ref: false,
-                    generics: vec![],
-                };
+                value_field_type = FieldType::new_with_user_type_id(
+                    type_info.get_type_id() as u32,
+                    type_info.get_user_type_id(),
+                    true,
+                    false,
+                    Vec::new(),
+                );
                 value_type_info = Some(type_info);
                 &value_field_type
             } else {
@@ -262,13 +224,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
             let (key_type_info, key_field_type);
             let key_type = if !key_declared {
                 let type_info = context.read_any_type_info()?;
-                key_field_type = FieldType {
-                    type_id: type_info.get_type_id() as u32,
-                    user_type_id: type_info.get_user_type_id(),
-                    nullable: true,
-                    track_ref: false,
-                    generics: vec![],
-                };
+                key_field_type = FieldType::new_with_user_type_id(
+                    type_info.get_type_id() as u32,
+                    type_info.get_user_type_id(),
+                    true,
+                    false,
+                    Vec::new(),
+                );
                 key_type_info = Some(type_info);
                 &key_field_type
             } else {
@@ -290,13 +252,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
         let (key_type_info, key_field_type);
         let key_type = if !key_declared {
             let type_info = context.read_any_type_info()?;
-            key_field_type = FieldType {
-                type_id: type_info.get_type_id() as u32,
-                user_type_id: type_info.get_user_type_id(),
-                nullable: true,
-                track_ref: false,
-                generics: vec![],
-            };
+            key_field_type = FieldType::new_with_user_type_id(
+                type_info.get_type_id() as u32,
+                type_info.get_user_type_id(),
+                true,
+                false,
+                Vec::new(),
+            );
             key_type_info = Some(type_info);
             &key_field_type
         } else {
@@ -308,13 +270,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
         let (value_type_info, value_field_type);
         let value_type = if !value_declared {
             let type_info = context.read_any_type_info()?;
-            value_field_type = FieldType {
-                type_id: type_info.get_type_id() as u32,
-                user_type_id: type_info.get_user_type_id(),
-                nullable: true,
-                track_ref: false,
-                generics: vec![],
-            };
+            value_field_type = FieldType::new_with_user_type_id(
+                type_info.get_type_id() as u32,
+                type_info.get_user_type_id(),
+                true,
+                false,
+                Vec::new(),
+            );
             value_type_info = Some(type_info);
             &value_field_type
         } else {
@@ -802,13 +764,7 @@ pub fn skip_enum_variant(
         0b1 => {
             // Unnamed variant, skip tuple data (which is serialized as a collection)
             // Tuple uses collection format but doesn't write type info, so skip directly
-            let field_type = FieldType {
-                type_id: types::LIST,
-                user_type_id: u32::MAX,
-                nullable: false,
-                track_ref: false,
-                generics: vec![UNKNOWN_FIELD_TYPE],
-            };
+            let field_type = FieldType::new(types::LIST, false, vec![unknown_field_type()]);
             skip_collection(context, &field_type)
         }
         0b10 => {
diff --git a/rust/fory-core/src/serializer/struct_.rs b/rust/fory-core/src/serializer/struct_.rs
index d22fe1626..7981a6dba 100644
--- a/rust/fory-core/src/serializer/struct_.rs
+++ b/rust/fory-core/src/serializer/struct_.rs
@@ -47,6 +47,11 @@ pub fn write_type_info<T: Serializer>(context: &mut WriteContext) -> Result<(),
     Ok(())
 }
 
+#[inline(always)]
+pub fn write_type_info_fast<T: StructSerializer>(context: &mut WriteContext) -> Result<(), Error> {
+    context.write_struct_type_info::<T>()
+}
+
 #[inline(always)]
 pub fn read_type_info<T: Serializer>(context: &mut ReadContext) -> Result<(), Error> {
     context.read_any_type_info()?;
diff --git a/rust/fory-core/src/serializer/trait_object.rs b/rust/fory-core/src/serializer/trait_object.rs
index bfbdfd9d0..bcba47675 100644
--- a/rust/fory-core/src/serializer/trait_object.rs
+++ b/rust/fory-core/src/serializer/trait_object.rs
@@ -64,17 +64,17 @@ macro_rules! downcast_and_serialize {
 ///
 /// ```rust,ignore
 /// use fory_core::{fory::Fory, register_trait_type, serializer::Serializer};
-/// use fory_derive::ForyObject;
+/// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
 ///
 /// trait Animal: Serializer {
 ///     fn speak(&self) -> String;
 ///     fn name(&self) -> &str;
 /// }
 ///
-/// #[derive(ForyObject, Debug)]
+/// #[derive(ForyStruct, Debug)]
 /// struct Dog { name: String }
 ///
-/// #[derive(ForyObject, Debug)]
+/// #[derive(ForyStruct, Debug)]
 /// struct Cat { name: String }
 ///
 /// impl Animal for Dog {
@@ -110,6 +110,7 @@ macro_rules! register_trait_type {
                 Box::new(<register_trait_type!(@first_type $($impl_type),+) as $crate::serializer::ForyDefault>::fory_default())
             }
         }
+        $crate::generate_box_trait_codec!($trait_name);
 
         // 2. Auto-generate Rc wrapper type and conversions
         $crate::generate_smart_pointer_wrapper!(
@@ -122,6 +123,7 @@ macro_rules! register_trait_type {
             store_rc_ref,
             $($impl_type),+
         );
+        $crate::generate_smart_pointer_codec!(std::rc::Rc, Rc, $trait_name);
 
         // 3. Auto-generate Arc wrapper type and conversions
         $crate::generate_smart_pointer_wrapper!(
@@ -134,6 +136,7 @@ macro_rules! register_trait_type {
             store_arc_ref,
             $($impl_type),+
         );
+        $crate::generate_smart_pointer_codec!(std::sync::Arc, Arc, $trait_name);
 
         // 4. Serializer implementation for Box<dyn Trait> (existing functionality)
         impl fory_core::Serializer for Box<dyn $trait_name> {
@@ -338,6 +341,363 @@ macro_rules! generate_smart_pointer_wrapper {
     };
 }
 
+#[macro_export]
+macro_rules! generate_smart_pointer_codec {
+    ($ptr_path:path, $ptr_name:ident, $trait_name:ident) => {
+        $crate::paste::paste! {
+            pub(crate) struct [<$trait_name $ptr_name Codec>]<const NULLABLE: bool, const TRACK_REF: bool>;
+
+            impl<const NULLABLE: bool, const TRACK_REF: bool>
+                fory_core::serializer::codec::Codec<$ptr_path<dyn $trait_name>>
+                for [<$trait_name $ptr_name Codec>]<NULLABLE, TRACK_REF>
+            {
+                #[inline(always)]
+                fn field_type(
+                    _type_resolver: &fory_core::resolver::TypeResolver,
+                ) -> Result<fory_core::meta::FieldType, fory_core::Error> {
+                    Ok(fory_core::meta::FieldType::new_with_ref(
+                        fory_core::type_id::TypeId::UNKNOWN as u32,
+                        NULLABLE,
+                        TRACK_REF,
+                        Vec::new(),
+                    ))
+                }
+
+                #[inline(always)]
+                fn reserved_space() -> usize {
+                    <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_reserved_space()
+                        + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+                }
+
+                #[inline(always)]
+                fn write_field(
+                    value: &$ptr_path<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                ) -> Result<(), fory_core::Error> {
+                    let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+                    <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write(
+                        &wrapper,
+                        context,
+                        if TRACK_REF {
+                            fory_core::RefMode::Tracking
+                        } else if NULLABLE {
+                            fory_core::RefMode::NullOnly
+                        } else {
+                            fory_core::RefMode::None
+                        },
+                        true,
+                        false,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_field(
+                    context: &mut fory_core::ReadContext,
+                ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(
+                        context,
+                        if TRACK_REF {
+                            fory_core::RefMode::Tracking
+                        } else if NULLABLE {
+                            fory_core::RefMode::NullOnly
+                        } else {
+                            fory_core::RefMode::None
+                        },
+                        true,
+                    )
+                }
+
+                #[inline(always)]
+                fn write_data(
+                    value: &$ptr_path<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                ) -> Result<(), fory_core::Error> {
+                    let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+                    <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write_data(
+                        &wrapper,
+                        context,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_data(
+                    context: &mut fory_core::ReadContext,
+                ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(context, fory_core::RefMode::None, true)
+                }
+
+                #[inline(always)]
+                fn read_field_with_type(
+                    context: &mut fory_core::ReadContext,
+                    remote_field_type: &fory_core::meta::FieldType,
+                ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(
+                        context,
+                        fory_core::serializer::codec::field_ref_mode(remote_field_type),
+                        true,
+                    )
+                }
+
+                #[inline(always)]
+                fn write_with_mode(
+                    value: &$ptr_path<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                    ref_mode: fory_core::RefMode,
+                    write_type_info: bool,
+                    has_generics: bool,
+                ) -> Result<(), fory_core::Error> {
+                    let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+                    <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write(
+                        &wrapper,
+                        context,
+                        ref_mode,
+                        write_type_info,
+                        has_generics,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_with_mode(
+                    context: &mut fory_core::ReadContext,
+                    ref_mode: fory_core::RefMode,
+                    read_type_info: bool,
+                ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+                    let wrapper = <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_read(
+                        context,
+                        ref_mode,
+                        read_type_info,
+                    )?;
+                    Ok($ptr_path::<dyn $trait_name>::from(wrapper))
+                }
+
+                #[inline(always)]
+                fn read_with_type_info(
+                    context: &mut fory_core::ReadContext,
+                    ref_mode: fory_core::RefMode,
+                    type_info: std::rc::Rc<fory_core::TypeInfo>,
+                ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+                    let wrapper =
+                        <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_read_with_type_info(
+                            context,
+                            ref_mode,
+                            type_info,
+                        )?;
+                    Ok($ptr_path::<dyn $trait_name>::from(wrapper))
+                }
+
+                #[inline(always)]
+                fn default_value() -> $ptr_path<dyn $trait_name> {
+                    $ptr_path::<dyn $trait_name>::from(
+                        <[<$trait_name $ptr_name>] as fory_core::ForyDefault>::fory_default(),
+                    )
+                }
+
+                #[inline(always)]
+                fn write_type_info(_context: &mut fory_core::WriteContext) -> Result<(), fory_core::Error> {
+                    Ok(())
+                }
+
+                #[inline(always)]
+                fn read_type_info(_context: &mut fory_core::ReadContext) -> Result<(), fory_core::Error> {
+                    Ok(())
+                }
+
+                #[inline(always)]
+                fn static_type_id() -> fory_core::TypeId {
+                    fory_core::TypeId::UNKNOWN
+                }
+
+                #[inline(always)]
+                fn is_polymorphic() -> bool {
+                    true
+                }
+
+                #[inline(always)]
+                fn is_shared_ref() -> bool {
+                    true
+                }
+
+                #[inline(always)]
+                fn concrete_type_id(value: &$ptr_path<dyn $trait_name>) -> std::any::TypeId {
+                    <dyn $trait_name as fory_core::Serializer>::as_any(&**value).type_id()
+                }
+            }
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! generate_box_trait_codec {
+    ($trait_name:ident) => {
+        $crate::paste::paste! {
+            pub(crate) struct [<$trait_name BoxCodec>]<const NULLABLE: bool, const TRACK_REF: bool>;
+
+            impl<const NULLABLE: bool, const TRACK_REF: bool>
+                fory_core::serializer::codec::Codec<Box<dyn $trait_name>>
+                for [<$trait_name BoxCodec>]<NULLABLE, TRACK_REF>
+            {
+                #[inline(always)]
+                fn field_type(
+                    _type_resolver: &fory_core::resolver::TypeResolver,
+                ) -> Result<fory_core::meta::FieldType, fory_core::Error> {
+                    Ok(fory_core::meta::FieldType::new_with_ref(
+                        fory_core::type_id::TypeId::UNKNOWN as u32,
+                        NULLABLE,
+                        TRACK_REF,
+                        Vec::new(),
+                    ))
+                }
+
+                #[inline(always)]
+                fn reserved_space() -> usize {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_reserved_space()
+                        + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+                }
+
+                #[inline(always)]
+                fn write_field(
+                    value: &Box<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                ) -> Result<(), fory_core::Error> {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_write(
+                        value,
+                        context,
+                        if TRACK_REF {
+                            fory_core::RefMode::Tracking
+                        } else if NULLABLE {
+                            fory_core::RefMode::NullOnly
+                        } else {
+                            fory_core::RefMode::None
+                        },
+                        true,
+                        false,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_field(
+                    context: &mut fory_core::ReadContext,
+                ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(
+                        context,
+                        if TRACK_REF {
+                            fory_core::RefMode::Tracking
+                        } else if NULLABLE {
+                            fory_core::RefMode::NullOnly
+                        } else {
+                            fory_core::RefMode::None
+                        },
+                        true,
+                    )
+                }
+
+                #[inline(always)]
+                fn write_data(
+                    value: &Box<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                ) -> Result<(), fory_core::Error> {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_write_data(
+                        value,
+                        context,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_data(
+                    context: &mut fory_core::ReadContext,
+                ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(context, fory_core::RefMode::None, true)
+                }
+
+                #[inline(always)]
+                fn read_field_with_type(
+                    context: &mut fory_core::ReadContext,
+                    remote_field_type: &fory_core::meta::FieldType,
+                ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+                    Self::read_with_mode(
+                        context,
+                        fory_core::serializer::codec::field_ref_mode(remote_field_type),
+                        true,
+                    )
+                }
+
+                #[inline(always)]
+                fn write_with_mode(
+                    value: &Box<dyn $trait_name>,
+                    context: &mut fory_core::WriteContext,
+                    ref_mode: fory_core::RefMode,
+                    write_type_info: bool,
+                    has_generics: bool,
+                ) -> Result<(), fory_core::Error> {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_write(
+                        value,
+                        context,
+                        ref_mode,
+                        write_type_info,
+                        has_generics,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_with_mode(
+                    context: &mut fory_core::ReadContext,
+                    ref_mode: fory_core::RefMode,
+                    read_type_info: bool,
+                ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_read(
+                        context,
+                        ref_mode,
+                        read_type_info,
+                    )
+                }
+
+                #[inline(always)]
+                fn read_with_type_info(
+                    context: &mut fory_core::ReadContext,
+                    ref_mode: fory_core::RefMode,
+                    type_info: std::rc::Rc<fory_core::TypeInfo>,
+                ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+                    <Box<dyn $trait_name> as fory_core::Serializer>::fory_read_with_type_info(
+                        context,
+                        ref_mode,
+                        type_info,
+                    )
+                }
+
+                #[inline(always)]
+                fn default_value() -> Box<dyn $trait_name> {
+                    <Box<dyn $trait_name> as fory_core::ForyDefault>::fory_default()
+                }
+
+                #[inline(always)]
+                fn write_type_info(_context: &mut fory_core::WriteContext) -> Result<(), fory_core::Error> {
+                    Ok(())
+                }
+
+                #[inline(always)]
+                fn read_type_info(_context: &mut fory_core::ReadContext) -> Result<(), fory_core::Error> {
+                    Ok(())
+                }
+
+                #[inline(always)]
+                fn static_type_id() -> fory_core::TypeId {
+                    fory_core::TypeId::UNKNOWN
+                }
+
+                #[inline(always)]
+                fn is_polymorphic() -> bool {
+                    true
+                }
+
+                #[inline(always)]
+                fn concrete_type_id(value: &Box<dyn $trait_name>) -> std::any::TypeId {
+                    <dyn $trait_name as fory_core::Serializer>::as_any(&**value).type_id()
+                }
+            }
+        }
+    };
+}
+
 /// Macro to read smart pointer trait objects (`Rc<dyn Trait>,` `Arc<dyn Trait>`)
 /// This macro handles ref tracking and directly constructs the trait object from concrete types
 #[macro_export]
diff --git a/rust/fory-core/src/serializer/weak.rs b/rust/fory-core/src/serializer/weak.rs
index f9f4e0849..8e26dcf37 100644
--- a/rust/fory-core/src/serializer/weak.rs
+++ b/rust/fory-core/src/serializer/weak.rs
@@ -126,10 +126,7 @@ impl<T: Serializer + ForyDefault + 'static> Serializer for RcWeak<T> {
     }
 
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
@@ -299,10 +296,7 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for ArcWeak
     }
 
     fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
-        match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
-            Ok(info) => Ok(info),
-            Err(e) => Err(Error::enhance_type_error::<T>(e)),
-        }
+        T::fory_get_type_info(type_resolver)
     }
 
     fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/util/mod.rs b/rust/fory-core/src/util/mod.rs
index 071094d95..85d3be0dc 100644
--- a/rust/fory-core/src/util/mod.rs
+++ b/rust/fory-core/src/util/mod.rs
@@ -33,6 +33,6 @@ pub const EPOCH: NaiveDate = match NaiveDate::from_ymd_opt(1970, 1, 1) {
     Some(epoch) => epoch,
 };
 
-/// Global flag to check if ENABLE_FORY_DEBUG_OUTPUT environment variable is set at compile time.
-/// Set ENABLE_FORY_DEBUG_OUTPUT=1 at compile time to enable debug output.
-pub const ENABLE_FORY_DEBUG_OUTPUT: bool = option_env!("ENABLE_FORY_DEBUG_OUTPUT").is_some();
+/// Set `ENABLE_FORY_DEBUG_OUTPUT=1` at compile time to enable debug output.
+#[allow(unexpected_cfgs)]
+pub const ENABLE_FORY_DEBUG_OUTPUT: bool = cfg!(fory_debug_output);
diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs
index 4ca12cbe6..f8a726ef1 100644
--- a/rust/fory-derive/src/lib.rs
+++ b/rust/fory-derive/src/lib.rs
@@ -22,24 +22,22 @@
 //!
 //! ## Available Macros
 //!
-//! ### `#[derive(ForyObject)]`
+//! ### `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, `#[derive(ForyUnion)]`
 //!
-//! Generates object serialization code for structs and enums. This macro
-//! implements the `Serializer` trait, enabling full object graph serialization
-//! with reference handling.
+//! Generates `Serializer` implementations for structs, pure enums, and tagged
+//! unions with payload variants.
 //!
 //! **Supported Types:**
-//! - Structs with named fields
-//! - Structs with unnamed fields (tuple structs)
-//! - Unit structs
-//! - Enums with variants
+//! - `ForyStruct`: named, tuple, and unit structs
+//! - `ForyEnum`: pure unit enums
+//! - `ForyUnion`: enums with payload variants
 //!
 //! **Example:**
 //! ```rust
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct};
 //! use std::collections::HashMap;
 //!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
 //! struct Person {
 //!     name: String,
 //!     age: i32,
@@ -48,13 +46,13 @@
 //!     metadata: HashMap<String, String>,
 //! }
 //!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
 //! struct Address {
 //!     street: String,
 //!     city: String,
 //! }
 //!
-//! #[derive(ForyObject, Debug, PartialEq, Default)]
+//! #[derive(ForyEnum, Debug, PartialEq, Default)]
 //! enum Status {
 //!     #[default]
 //!     Active,
@@ -90,7 +88,7 @@
 //!
 //! ## Generated Code
 //!
-//! ### For `#[derive(ForyObject)]`
+//! ### For `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]`
 //!
 //! The macro generates:
 //! - `Serializer` trait implementation
@@ -116,7 +114,7 @@
 //! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be ignored by the
 //!   generated serializer, retaining compatibility with previous releases.
 //! - **`#[fory(generate_default)]`**: Enables the macro to generate `Default` implementation.
-//!   By default, `ForyObject` does NOT generate `impl Default` to avoid conflicts with existing
+//!   By default, `ForyStruct` does NOT generate `impl Default` to avoid conflicts with existing
 //!   `Default` implementations. Use this attribute when you want the macro to generate both
 //!   `ForyDefault` and `Default` for you.
 //!
@@ -148,9 +146,9 @@
 //!
 //! ```rust
 //! use fory_core::{fory::Fory, error::Error};
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
 //!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
 //! struct MyData {
 //!     value: i32,
 //!     text: String,
@@ -182,42 +180,79 @@
 
 use fory_row::derive_row;
 use proc_macro::TokenStream;
-use syn::{parse_macro_input, spanned::Spanned, Attribute, DeriveInput, LitBool};
+use syn::{parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, LitBool};
 
 mod fory_row;
 mod object;
 mod util;
 
-/// Derive macro for object serialization.
-///
-/// This macro generates code to implement the `Serializer` trait for the
-/// annotated type, enabling full object graph serialization with reference
-/// handling and cross-language compatibility.
-///
-/// # Example
-///
-/// ```rust
-/// use fory_derive::ForyObject;
-///
-/// #[derive(ForyObject, Debug, PartialEq)]
-/// struct Person {
-///     name: String,
-///     age: i32,
-///     address: Address,
-/// }
-///
-/// #[derive(ForyObject, Debug, PartialEq)]
-/// struct Address {
-///     street: String,
-///     city: String,
-/// }
-/// ```
-#[proc_macro_derive(ForyObject, attributes(fory))]
-pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) -> TokenStream {
+/// Derive macro for struct serialization.
+#[proc_macro_derive(ForyStruct, attributes(fory))]
+pub fn proc_macro_derive_fory_struct(input: proc_macro::TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    if !matches!(input.data, Data::Struct(_)) {
+        return syn::Error::new(
+            input.ident.span(),
+            "ForyStruct can only be derived for structs; use ForyEnum for pure enums or ForyUnion for data-carrying enums",
+        )
+        .into_compile_error()
+        .into();
+    }
+    derive_serializer(input)
+}
+
+/// Derive macro for pure enum serialization.
+#[proc_macro_derive(ForyEnum, attributes(fory))]
+pub fn proc_macro_derive_fory_enum(input: proc_macro::TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
+    let Data::Enum(data_enum) = &input.data else {
+        return syn::Error::new(input.ident.span(), "ForyEnum can only be derived for enums")
+            .into_compile_error()
+            .into();
+    };
+    if data_enum
+        .variants
+        .iter()
+        .any(|variant| !matches!(variant.fields, Fields::Unit))
+    {
+        return syn::Error::new(
+            input.ident.span(),
+            "ForyEnum is only for pure unit enums; use ForyUnion for enum variants with payloads",
+        )
+        .into_compile_error()
+        .into();
+    }
+    derive_serializer(input)
+}
+
+/// Derive macro for tagged union serialization.
+#[proc_macro_derive(ForyUnion, attributes(fory))]
+pub fn proc_macro_derive_fory_union(input: proc_macro::TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let Data::Enum(data_enum) = &input.data else {
+        return syn::Error::new(
+            input.ident.span(),
+            "ForyUnion can only be derived for enums",
+        )
+        .into_compile_error()
+        .into();
+    };
+    if data_enum
+        .variants
+        .iter()
+        .all(|variant| matches!(variant.fields, Fields::Unit))
+    {
+        return syn::Error::new(
+            input.ident.span(),
+            "ForyUnion requires at least one payload variant; use ForyEnum for pure unit enums",
+        )
+        .into_compile_error()
+        .into();
+    }
+    derive_serializer(input)
+}
 
-    // Check if this is being applied to a trait (which is not possible with derive macros)
-    // Derive macros can only be applied to structs, enums, and unions
+fn derive_serializer(input: DeriveInput) -> TokenStream {
     let attrs = match parse_fory_attrs(&input.attrs) {
         Ok(attrs) => attrs,
         Err(err) => return err.into_compile_error().into(),
diff --git a/rust/fory-derive/src/object/derive_enum.rs b/rust/fory-derive/src/object/derive_enum.rs
index dbeaf5729..d394b5fe0 100644
--- a/rust/fory-derive/src/object/derive_enum.rs
+++ b/rust/fory-derive/src/object/derive_enum.rs
@@ -15,12 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use super::field_codec::{build_bindings, FieldBinding};
 use super::util::{enum_variant_id, is_default_value_variant, is_skip_enum_variant};
 use crate::object::misc;
-use crate::object::read::gen_read_field;
 use crate::object::util::{get_filtered_fields_iter, get_sorted_field_names};
-use crate::object::write::gen_write_field;
-use crate::util::{extract_fields, source_fields};
+use crate::util::{extract_fields, source_fields, SourceField};
 use proc_macro2::{Ident, TokenStream};
 use quote::quote;
 use syn::{DataEnum, Fields};
@@ -28,6 +27,175 @@ fn temp_var_name(i: usize) -> String {
     format!("f{}", i)
 }
 
+fn gen_write_named_variant_fields(
+    source_fields: &[crate::util::SourceField<'_>],
+    field_idents: &[&syn::Ident],
+) -> Vec<TokenStream> {
+    match build_bindings(source_fields) {
+        Ok(bindings) => bindings
+            .iter()
+            .zip(field_idents.iter())
+            .filter_map(|(binding, ident)| match binding {
+                FieldBinding::Codec(binding) => Some(binding.write_value_field(quote! { #ident })),
+                FieldBinding::Skipped(_) => None,
+            })
+            .collect(),
+        Err(err) => vec![err.to_compile_error()],
+    }
+}
+
+fn unnamed_source_fields(fields: &syn::FieldsUnnamed) -> Vec<SourceField<'_>> {
+    fields
+        .unnamed
+        .iter()
+        .enumerate()
+        .map(|(idx, field)| SourceField {
+            original_index: idx,
+            field,
+            field_name: idx.to_string(),
+            is_tuple_struct: true,
+        })
+        .collect()
+}
+
+fn gen_write_variant_fields(
+    source_fields: &[SourceField<'_>],
+    field_idents: &[Ident],
+) -> Vec<TokenStream> {
+    match build_bindings(source_fields) {
+        Ok(bindings) => bindings
+            .iter()
+            .zip(field_idents.iter())
+            .filter_map(|(binding, ident)| match binding {
+                FieldBinding::Codec(binding) => Some(binding.write_value_field(quote! { #ident })),
+                FieldBinding::Skipped(_) => None,
+            })
+            .collect(),
+        Err(err) => vec![err.to_compile_error()],
+    }
+}
+
+fn gen_write_variant_elements(
+    source_fields: &[SourceField<'_>],
+    field_idents: &[Ident],
+) -> Vec<TokenStream> {
+    match build_bindings(source_fields) {
+        Ok(bindings) => bindings
+            .iter()
+            .zip(field_idents.iter())
+            .filter_map(|(binding, ident)| match binding {
+                FieldBinding::Codec(binding) => Some(binding.write_value_with_mode(
+                    quote! { #ident },
+                    quote! { fory_core::RefMode::NullOnly },
+                    quote! { true },
+                )),
+                FieldBinding::Skipped(_) => None,
+            })
+            .collect(),
+        Err(err) => vec![err.to_compile_error()],
+    }
+}
+
+fn gen_write_single_payload(source_fields: &[SourceField<'_>], value: TokenStream) -> TokenStream {
+    match build_bindings(source_fields) {
+        Ok(bindings) => match bindings.as_slice() {
+            [FieldBinding::Codec(binding)] => binding.write_value_with_mode(
+                value,
+                quote! { fory_core::RefMode::Tracking },
+                quote! { true },
+            ),
+            [FieldBinding::Skipped(_)] => {
+                quote! { compile_error!("skip is not valid for union payload fields"); }
+            }
+            _ => quote! { compile_error!("union payload helper requires exactly one field"); },
+        },
+        Err(err) => err.to_compile_error(),
+    }
+}
+
+fn gen_read_variant_fields(source_fields: &[SourceField<'_>]) -> (Vec<TokenStream>, Vec<Ident>) {
+    match build_bindings(source_fields) {
+        Ok(bindings) => {
+            let read_fields = bindings
+                .iter()
+                .map(|binding| match binding {
+                    FieldBinding::Codec(binding) => binding.read_field(),
+                    FieldBinding::Skipped(binding) => binding.read_default(),
+                })
+                .collect();
+            let private_idents = bindings
+                .iter()
+                .map(|binding| match binding {
+                    FieldBinding::Codec(binding) => binding.private_ident.clone(),
+                    FieldBinding::Skipped(binding) => binding.private_ident.clone(),
+                })
+                .collect();
+            (read_fields, private_idents)
+        }
+        Err(err) => (vec![err.to_compile_error()], Vec::new()),
+    }
+}
+
+fn gen_read_variant_elements(
+    source_fields: &[SourceField<'_>],
+) -> (Vec<TokenStream>, Vec<Ident>, usize) {
+    match build_bindings(source_fields) {
+        Ok(bindings) => {
+            let mut serialized_index = 0usize;
+            let mut read_fields = Vec::with_capacity(bindings.len());
+            let mut private_idents = Vec::with_capacity(bindings.len());
+            for binding in bindings {
+                match binding {
+                    FieldBinding::Codec(binding) => {
+                        let var = binding.private_ident.clone();
+                        let default_expr =
+                            super::field_codec::default_expr_for_type(binding.value_ty);
+                        let index = serialized_index;
+                        serialized_index += 1;
+                        let read_value = binding.read_with_mode_expr(
+                            quote! { fory_core::RefMode::NullOnly },
+                            quote! { true },
+                        );
+                        read_fields.push(quote! {
+                            let #var = if #index < len {
+                                #read_value
+                            } else {
+                                #default_expr
+                            };
+                        });
+                        private_idents.push(var);
+                    }
+                    FieldBinding::Skipped(binding) => {
+                        let var = binding.private_ident;
+                        let default_expr =
+                            super::field_codec::default_expr_for_type(&binding.source.field.ty);
+                        read_fields.push(quote! {
+                            let #var = #default_expr;
+                        });
+                        private_idents.push(var);
+                    }
+                }
+            }
+            (read_fields, private_idents, serialized_index)
+        }
+        Err(err) => (vec![err.to_compile_error()], Vec::new(), 0),
+    }
+}
+
+fn gen_read_single_payload(source_fields: &[SourceField<'_>]) -> TokenStream {
+    match build_bindings(source_fields) {
+        Ok(bindings) => match bindings.as_slice() {
+            [FieldBinding::Codec(binding)] => binding
+                .read_with_mode_expr(quote! { fory_core::RefMode::Tracking }, quote! { true }),
+            [FieldBinding::Skipped(_)] => {
+                quote! { compile_error!("skip is not valid for union payload fields") }
+            }
+            _ => quote! { compile_error!("union payload helper requires exactly one field") },
+        },
+        Err(err) => err.to_compile_error(),
+    }
+}
+
 /// For Union-compatible enums with data variants, return UNION TypeId in xlang mode.
 pub fn gen_actual_type_id(data_enum: &DataEnum) -> TokenStream {
     let is_union_compatible = is_union_compatible_enum(data_enum);
@@ -223,12 +391,13 @@ fn xlang_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> V
                 }
                 Fields::Unnamed(fields_unnamed) => {
                     if is_union_compatible && fields_unnamed.unnamed.len() == 1 {
-                        // Union-compatible single field: write tag + value with type info (like xwriteRef)
+                        let source_fields = unnamed_source_fields(fields_unnamed);
+                        let write_payload =
+                            gen_write_single_payload(&source_fields, quote! { value });
                         quote! {
                             Self::#ident(ref value) => {
                                 context.writer.write_var_u32(#tag_value);
-                                use fory_core::serializer::Serializer;
-                                value.fory_write(context, fory_core::RefMode::Tracking, true, false)?;
+                                #write_payload
                             }
                         }
                     } else {
@@ -241,14 +410,16 @@ fn xlang_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> V
                 }
                 Fields::Named(fields_named) => {
                     if is_union_compatible && fields_named.named.len() == 1 {
-                        // Union-compatible single field: write tag + value with type info (like xwriteRef)
                         let field_ident =
                             fields_named.named.first().unwrap().ident.as_ref().unwrap();
+                        let fields_clone = syn::Fields::Named(fields_named.clone());
+                        let source_fields = source_fields(&fields_clone);
+                        let write_payload =
+                            gen_write_single_payload(&source_fields, quote! { #field_ident });
                         quote! {
                             Self::#ident { ref #field_ident } => {
                                 context.writer.write_var_u32(#tag_value);
-                                use fory_core::serializer::Serializer;
-                                #field_ident.fory_write(context, fory_core::RefMode::Tracking, true, false)?;
+                                #write_payload
                             }
                         }
                     } else {
@@ -285,16 +456,12 @@ fn rust_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> Ve
                     }
                 }
                 Fields::Unnamed(fields_unnamed) => {
+                    let source_fields = unnamed_source_fields(fields_unnamed);
                     let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
                         .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
                         .collect();
 
-                    let write_fields: Vec<_> = fields_unnamed
-                        .unnamed
-                        .iter()
-                        .zip(field_idents.iter())
-                        .map(|(f, ident)| gen_write_field(f, ident, false))
-                        .collect();
+                    let write_fields = gen_write_variant_fields(&source_fields, &field_idents);
 
                     quote! {
                         Self::#ident( #(#field_idents),* ) => {
@@ -314,11 +481,8 @@ fn rust_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> Ve
                         .map(|sf| sf.field.ident.as_ref().unwrap())
                         .collect();
 
-                    let write_fields: Vec<_> = source_fields
-                        .iter()
-                        .zip(field_idents.iter())
-                        .map(|(sf, ident)| gen_write_field(sf.field, ident, false))
-                        .collect();
+                    let write_fields =
+                        gen_write_named_variant_fields(&source_fields, &field_idents);
 
                     quote! {
                         Self::#ident { #(#field_idents),* } => {
@@ -360,11 +524,12 @@ fn rust_compatible_variant_write_branches(
                 }
                 Fields::Unnamed(fields_unnamed) => {
                     // For unnamed enum variants, write using collection format (same protocol as tuple)
+                    let source_fields = unnamed_source_fields(fields_unnamed);
                     let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
                         .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
                         .collect();
-
-                    let field_count = fields_unnamed.unnamed.len();
+                    let write_fields = gen_write_variant_elements(&source_fields, &field_idents);
+                    let field_count = write_fields.len();
 
                     quote! {
                         Self::#ident( #(ref #field_idents),* ) => {
@@ -373,10 +538,7 @@ fn rust_compatible_variant_write_branches(
                             context.writer.write_var_u32(#field_count as u32);
                             let header = 0u8; // No IS_SAME_TYPE flag
                             context.writer.write_u8(header);
-                            use fory_core::serializer::Serializer;
-                            #(
-                                #field_idents.fory_write(context, fory_core::RefMode::NullOnly, true, false)?;
-                            )*
+                            #(#write_fields)*
                         }
                     }
                 }
@@ -384,7 +546,7 @@ fn rust_compatible_variant_write_branches(
                     // Generate meta type identifier for this named variant
                     let meta_type_ident = Ident::new(
                         &format!("{}_{}VariantMeta", enum_name, ident),
-                        proc_macro2::Span::call_site()
+                        proc_macro2::Span::call_site(),
                     );
                     let fields_clone = syn::Fields::Named(fields_named.clone());
                     let source_fields = source_fields(&fields_clone);
@@ -393,11 +555,8 @@ fn rust_compatible_variant_write_branches(
                         .map(|sf| sf.field.ident.as_ref().unwrap())
                         .collect();
 
-                    let write_fields: Vec<_> = source_fields
-                        .iter()
-                        .zip(field_idents.iter())
-                        .map(|(sf, ident)| gen_write_field(sf.field, ident, false))
-                        .collect();
+                    let write_fields =
+                        gen_write_named_variant_fields(&source_fields, &field_idents);
 
                     quote! {
                         Self::#ident { #(#field_idents),* } => {
@@ -560,12 +719,11 @@ fn xlang_variant_read_branches(
                 }
                 Fields::Unnamed(fields_unnamed) => {
                     if is_union_compatible && fields_unnamed.unnamed.len() == 1 {
-                        // Union-compatible single field: read value with ref_info=Tracking, type_info=true
-                        let field_ty = &fields_unnamed.unnamed.first().unwrap().ty;
+                        let source_fields = unnamed_source_fields(fields_unnamed);
+                        let read_payload = gen_read_single_payload(&source_fields);
                         quote! {
                             #tag_value => {
-                                use fory_core::serializer::Serializer;
-                                let value = <#field_ty as Serializer>::fory_read(context, fory_core::RefMode::Tracking, true)?;
+                                let value = #read_payload;
                                 Ok(Self::#ident(value))
                             }
                         }
@@ -573,10 +731,7 @@ fn xlang_variant_read_branches(
                         let default_fields: Vec<TokenStream> = fields_unnamed
                             .unnamed
                             .iter()
-                            .map(|f| {
-                                let ty = &f.ty;
-                                quote! { <#ty as fory_core::ForyDefault>::fory_default() }
-                            })
+                            .map(|f| super::field_codec::default_expr_for_type(&f.ty))
                             .collect();
                         quote! {
                             #tag_value => Ok(Self::#ident( #(#default_fields),* )),
@@ -585,14 +740,14 @@ fn xlang_variant_read_branches(
                 }
                 Fields::Named(fields_named) => {
                     if is_union_compatible && fields_named.named.len() == 1 {
-                        // Union-compatible single field: read value with ref_info=Tracking, type_info=true
                         let field = fields_named.named.first().unwrap();
                         let field_ident = field.ident.as_ref().unwrap();
-                        let field_ty = &field.ty;
+                        let fields_clone = syn::Fields::Named(fields_named.clone());
+                        let source_fields = source_fields(&fields_clone);
+                        let read_payload = gen_read_single_payload(&source_fields);
                         quote! {
                             #tag_value => {
-                                use fory_core::serializer::Serializer;
-                                let value = <#field_ty as Serializer>::fory_read(context, fory_core::RefMode::Tracking, true)?;
+                                let value = #read_payload;
                                 Ok(Self::#ident { #field_ident: value })
                             }
                         }
@@ -602,8 +757,8 @@ fn xlang_variant_read_branches(
                             .iter()
                             .map(|f| {
                                 let field_ident = f.ident.as_ref().unwrap();
-                                let ty = &f.ty;
-                                quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+                                let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+                                quote! { #field_ident: #default_expr }
                             })
                             .collect();
                         quote! {
@@ -638,20 +793,8 @@ fn rust_variant_read_branches(
                     }
                 }
                 Fields::Unnamed(fields_unnamed) => {
-                    let field_idents: Vec<Ident> = (0..fields_unnamed.unnamed.len())
-                        .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
-                        .collect();
-
-                    let read_fields: Vec<TokenStream> = fields_unnamed
-                        .unnamed
-                        .iter()
-                        .enumerate()
-                        .zip(field_idents.iter())
-                        .map(|((idx, f), ident)| {
-                            let field_name = idx.to_string();
-                            gen_read_field(f, ident, &field_name)
-                        })
-                        .collect();
+                    let source_fields = unnamed_source_fields(fields_unnamed);
+                    let (read_fields, field_idents) = gen_read_variant_fields(&source_fields);
 
                     quote! {
                         #tag_value => {
@@ -664,24 +807,20 @@ fn rust_variant_read_branches(
                     let fields_clone = syn::Fields::Named(fields_named.clone());
                     let source_fields = source_fields(&fields_clone);
 
-                    let field_idents: Vec<_> = source_fields
-                        .iter()
-                        .map(|sf| sf.field.ident.as_ref().unwrap())
-                        .collect();
-
-                    let read_fields: Vec<_> = source_fields
+                    let (read_fields, private_idents) = gen_read_variant_fields(&source_fields);
+                    let field_inits: Vec<_> = source_fields
                         .iter()
-                        .zip(field_idents.iter())
-                        .map(|(sf, ident)| {
-                            let field_name = ident.to_string();
-                            gen_read_field(sf.field, ident, &field_name)
+                        .zip(private_idents.iter())
+                        .map(|(sf, private_ident)| {
+                            let field_ident = sf.field.ident.as_ref().unwrap();
+                            quote! { #field_ident: #private_ident }
                         })
                         .collect();
 
                     quote! {
                         #tag_value => {
                             #(#read_fields;)*
-                            Ok(Self::#ident { #(#field_idents),* })
+                            Ok(Self::#ident { #(#field_inits),* })
                         }
                     }
                 }
@@ -725,38 +864,14 @@ fn rust_compatible_variant_read_branches(
                 }
                 Fields::Unnamed(fields_unnamed) => {
                     // For unnamed enum variants, read using collection format (same protocol as tuple)
-                    let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
-                        .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
-                        .collect();
-
-                    let field_count = fields_unnamed.unnamed.len();
-
-                    let read_fields: Vec<TokenStream> = fields_unnamed
-                        .unnamed
-                        .iter()
-                        .enumerate()
-                        .map(|(i, field)| {
-                            let field_ident = &field_idents[i];
-                            let field_ty = &field.ty;
-                            quote! {
-                                let #field_ident = if #i < len {
-                                    use fory_core::serializer::Serializer;
-                                    <#field_ty>::fory_read(context, fory_core::RefMode::NullOnly, true)?
-                                } else {
-                                    <#field_ty as fory_core::ForyDefault>::fory_default()
-                                }
-                            }
-                        })
-                        .collect();
+                    let source_fields = unnamed_source_fields(fields_unnamed);
+                    let (read_fields, field_idents, field_count) =
+                        gen_read_variant_elements(&source_fields);
 
                     // Generate default value for this variant
-                    let default_fields: Vec<TokenStream> = fields_unnamed
-                        .unnamed
+                    let default_fields: Vec<TokenStream> = source_fields
                         .iter()
-                        .map(|f| {
-                            let ty = &f.ty;
-                            quote! { <#ty as fory_core::ForyDefault>::fory_default() }
-                        })
+                        .map(|sf| super::field_codec::default_expr_for_type(&sf.field.ty))
                         .collect();
                     let default_value = quote! { Self::#ident( #(#default_fields),* ) };
 
@@ -805,8 +920,8 @@ fn rust_compatible_variant_read_branches(
                         .iter()
                         .map(|f| {
                             let field_ident = f.ident.as_ref().unwrap();
-                            let ty = &f.ty;
-                            quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+                            let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+                            quote! { #field_ident: #default_expr }
                         })
                         .collect();
                     let default_value = quote! { Self::#ident { #(#default_fields),* } };
@@ -869,10 +984,7 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
             let default_fields: Vec<TokenStream> = fields_unnamed
                 .unnamed
                 .iter()
-                .map(|f| {
-                    let ty = &f.ty;
-                    quote! { <#ty as fory_core::ForyDefault>::fory_default() }
-                })
+                .map(|f| super::field_codec::default_expr_for_type(&f.ty))
                 .collect();
             quote! { Self::#default_variant_ident( #(#default_fields),* ) }
         }
@@ -882,8 +994,8 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
                 .iter()
                 .map(|f| {
                     let field_ident = f.ident.as_ref().unwrap();
-                    let ty = &f.ty;
-                    quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+                    let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+                    quote! { #field_ident: #default_expr }
                 })
                 .collect();
             quote! { Self::#default_variant_ident { #(#default_fields),* } }
diff --git a/rust/fory-derive/src/object/field_codec.rs b/rust/fory-derive/src/object/field_codec.rs
new file mode 100644
index 000000000..fcd03890e
--- /dev/null
+++ b/rust/fory-derive/src/object/field_codec.rs
@@ -0,0 +1,1175 @@
+// 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.
+
+use super::field_meta::{
+    classify_field_type, extract_option_inner_type, is_option_type, parse_field_meta,
+    ForyFieldMeta, IntEncoding,
+};
+use super::read::create_private_field_name;
+use super::util::get_type_id_by_type_ast;
+use crate::util::{is_arc_dyn_trait, is_box_dyn_trait, is_rc_dyn_trait, SourceField};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{GenericArgument, PathArguments, Type};
+
+pub(crate) struct ResolvedField<'a> {
+    pub source: &'a SourceField<'a>,
+    pub private_ident: syn::Ident,
+    pub dispatch: FieldDispatch,
+    pub value_ty: &'a Type,
+    pub field_id: i16,
+}
+
+pub(crate) enum FieldDispatch {
+    Codec {
+        codec_ty: TokenStream,
+    },
+    Serializer {
+        field_type: TokenStream,
+        has_generics: bool,
+    },
+}
+
+impl<'a> ResolvedField<'a> {
+    #[inline]
+    pub fn codec_call(&self) -> TokenStream {
+        match &self.dispatch {
+            FieldDispatch::Codec { codec_ty } => {
+                let value_ty = self.value_ty;
+                quote! { <#codec_ty as fory_core::serializer::codec::Codec<#value_ty>> }
+            }
+            FieldDispatch::Serializer { .. } => {
+                quote! { compile_error!("serializer-dispatched field has no codec call") }
+            }
+        }
+    }
+
+    pub fn reserved_space(&self) -> TokenStream {
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! { #call::reserved_space() }
+            }
+            FieldDispatch::Serializer { .. } => {
+                let ty = self.value_ty;
+                quote! {
+                    <#ty as fory_core::Serializer>::fory_reserved_space()
+                        + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+                }
+            }
+        }
+    }
+
+    pub fn write_field(&self) -> TokenStream {
+        let access =
+            super::util::get_field_accessor(self.source.field, self.source.original_index, true);
+        self.write_value_field(quote! { &#access })
+    }
+
+    pub fn write_value_field(&self, value: TokenStream) -> TokenStream {
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    #call::write_field(#value, context)?;
+                }
+            }
+            FieldDispatch::Serializer { has_generics, .. } => {
+                let ty = self.value_ty;
+                if serializer_field_can_use_data_path(self.source.field) {
+                    quote! {
+                        <#ty as fory_core::Serializer>::fory_write_data_generic(
+                            #value,
+                            context,
+                            #has_generics
+                        )?;
+                    }
+                } else {
+                    let ref_mode = serializer_ref_mode_for_field(self.source.field);
+                    quote! {
+                        let write_type_info = if context.is_compatible() {
+                            fory_core::type_id::need_to_write_type_for_field(
+                                <#ty as fory_core::Serializer>::fory_static_type_id()
+                            )
+                        } else {
+                            <#ty as fory_core::Serializer>::fory_is_polymorphic()
+                        };
+                        <#ty as fory_core::Serializer>::fory_write(
+                            #value,
+                            context,
+                            #ref_mode,
+                            write_type_info,
+                            #has_generics
+                        )?;
+                    }
+                }
+            }
+        }
+    }
+
+    pub fn write_value_with_mode(
+        &self,
+        value: TokenStream,
+        ref_mode: TokenStream,
+        write_type_info: TokenStream,
+    ) -> TokenStream {
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    #call::write_with_mode(
+                        #value,
+                        context,
+                        #ref_mode,
+                        #write_type_info,
+                        false,
+                    )?;
+                }
+            }
+            FieldDispatch::Serializer { has_generics, .. } => {
+                let ty = self.value_ty;
+                quote! {
+                    <#ty as fory_core::Serializer>::fory_write(
+                        #value,
+                        context,
+                        #ref_mode,
+                        #write_type_info,
+                        #has_generics,
+                    )?;
+                }
+            }
+        }
+    }
+
+    pub fn read_field(&self) -> TokenStream {
+        let var = &self.private_ident;
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    let #var = #call::read_field(context)?;
+                }
+            }
+            FieldDispatch::Serializer { .. } => {
+                let ty = self.value_ty;
+                if serializer_field_can_use_data_path(self.source.field) {
+                    quote! {
+                        let #var = <#ty as fory_core::Serializer>::fory_read_data(context)?;
+                    }
+                } else {
+                    let ref_mode = serializer_ref_mode_for_field(self.source.field);
+                    quote! {
+                        let read_type_info = if context.is_compatible() {
+                            fory_core::serializer::util::field_need_read_type_info(
+                                <#ty as fory_core::Serializer>::fory_static_type_id() as u32
+                            )
+                        } else {
+                            <#ty as fory_core::Serializer>::fory_is_polymorphic()
+                        };
+                        let #var = <#ty as fory_core::Serializer>::fory_read(
+                            context,
+                            #ref_mode,
+                            read_type_info
+                        )?;
+                    }
+                }
+            }
+        }
+    }
+
+    pub fn read_with_mode_expr(
+        &self,
+        ref_mode: TokenStream,
+        read_type_info: TokenStream,
+    ) -> TokenStream {
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    #call::read_with_mode(context, #ref_mode, #read_type_info)?
+                }
+            }
+            FieldDispatch::Serializer { .. } => {
+                let ty = self.value_ty;
+                quote! {
+                    <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, #read_type_info)?
+                }
+            }
+        }
+    }
+
+    pub fn declare_compatible_var(&self) -> TokenStream {
+        let var = &self.private_ident;
+        let ty = self.value_ty;
+        quote! {
+            let mut #var: Option<#ty> = None;
+        }
+    }
+
+    pub fn assign_value(&self) -> TokenStream {
+        let var = &self.private_ident;
+        let default_expr = default_expr_for_type(self.value_ty);
+        quote! {
+            #var.unwrap_or_else(|| #default_expr)
+        }
+    }
+
+    pub fn read_compatible(&self) -> TokenStream {
+        let var = &self.private_ident;
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    let remote_field_type = &_field.field_type;
+                    if let Some(value) = #call::read_compatible(
+                        context,
+                        local_field_type,
+                        remote_field_type,
+                    )? {
+                        #var = Some(value);
+                    } else {
+                        let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
+                            remote_field_type.type_id,
+                            remote_field_type.nullable,
+                        );
+                        fory_core::serializer::skip::skip_field_value(context, remote_field_type, read_ref_flag)?;
+                    }
+                }
+            }
+            FieldDispatch::Serializer { .. } => {
+                let ty = self.value_ty;
+                quote! {
+                    let remote_field_type = &_field.field_type;
+                    if fory_core::serializer::codec::field_types_compatible(
+                        local_field_type,
+                        remote_field_type,
+                    ) {
+                        let read_ref_mode =
+                            fory_core::serializer::codec::field_ref_mode(remote_field_type);
+                        let read_type_info = if context.is_compatible() {
+                            fory_core::serializer::util::field_need_read_type_info(
+                                remote_field_type.type_id
+                            )
+                        } else {
+                            <#ty as fory_core::Serializer>::fory_is_polymorphic()
+                        };
+                        #var = Some(<#ty as fory_core::Serializer>::fory_read(
+                            context,
+                            read_ref_mode,
+                            read_type_info,
+                        )?);
+                    } else {
+                        let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
+                            remote_field_type.type_id,
+                            remote_field_type.nullable,
+                        );
+                        fory_core::serializer::skip::skip_field_value(context, remote_field_type, read_ref_flag)?;
+                    }
+                }
+            }
+        }
+    }
+
+    pub fn field_info(&self) -> TokenStream {
+        let field_id = self.field_id;
+        let name = &self.source.field_name;
+        match &self.dispatch {
+            FieldDispatch::Codec { .. } => {
+                let call = self.codec_call();
+                quote! {
+                    fory_core::meta::FieldInfo::new_with_id(
+                        #field_id,
+                        #name,
+                        #call::field_type(type_resolver)?
+                    )
+                }
+            }
+            FieldDispatch::Serializer { field_type, .. } => {
+                quote! {
+                    fory_core::meta::FieldInfo::new_with_id(
+                        #field_id,
+                        #name,
+                        #field_type
+                    )
+                }
+            }
+        }
+    }
+}
+
+pub(crate) struct SkippedField<'a> {
+    pub source: &'a SourceField<'a>,
+    pub private_ident: syn::Ident,
+}
+
+impl<'a> SkippedField<'a> {
+    pub fn read_default(&self) -> TokenStream {
+        let var = &self.private_ident;
+        let default_expr = default_expr_for_type(&self.source.field.ty);
+        quote! {
+            let #var = #default_expr;
+        }
+    }
+
+    pub fn assign_value(&self) -> TokenStream {
+        let var = &self.private_ident;
+        quote! { #var }
+    }
+}
+
+pub(crate) enum FieldBinding<'a> {
+    Codec(ResolvedField<'a>),
+    Skipped(SkippedField<'a>),
+}
+
+pub(crate) fn build_bindings<'a>(
+    source_fields: &'a [SourceField<'a>],
+) -> syn::Result<Vec<FieldBinding<'a>>> {
+    source_fields
+        .iter()
+        .map(|source| {
+            let meta = parse_field_meta(source.field)?;
+            let private_ident = create_private_field_name(source.field, source.original_index);
+            if meta.skip {
+                return Ok(FieldBinding::Skipped(SkippedField {
+                    source,
+                    private_ident,
+                }));
+            }
+            let type_class = classify_field_type(&source.field.ty);
+            let is_outer_option = is_option_type(&source.field.ty);
+            let nullable = meta.effective_nullable(type_class) || is_outer_option;
+            let track_ref = meta.effective_ref(type_class);
+            let field_id = if meta.uses_tag_id() {
+                meta.effective_id() as i16
+            } else {
+                -1
+            };
+            let dispatch = field_dispatch_for(&source.field.ty, &meta, nullable, track_ref)?;
+            Ok(FieldBinding::Codec(ResolvedField {
+                source,
+                private_ident,
+                dispatch,
+                value_ty: &source.field.ty,
+                field_id,
+            }))
+        })
+        .collect()
+}
+
+fn field_dispatch_for(
+    ty: &Type,
+    meta: &ForyFieldMeta,
+    nullable: bool,
+    track_ref: bool,
+) -> syn::Result<FieldDispatch> {
+    if meta.encoding.is_none()
+        && meta.list.is_none()
+        && meta.map.is_none()
+        && is_container_type(ty)
+        && !contains_custom_trait_object(ty)
+        && !contains_exact_any_object(ty)
+    {
+        return Ok(FieldDispatch::Serializer {
+            field_type: field_type_expr_for(ty, nullable, track_ref)?,
+            has_generics: true,
+        });
+    }
+    Ok(FieldDispatch::Codec {
+        codec_ty: codec_type_for(ty, meta, nullable, track_ref)?,
+    })
+}
+
+pub(crate) fn codec_type_for(
+    ty: &Type,
+    meta: &ForyFieldMeta,
+    nullable: bool,
+    track_ref: bool,
+) -> syn::Result<TokenStream> {
+    if let Some(inner) = extract_option_inner_type(ty) {
+        let inner_meta = ForyFieldMeta {
+            nullable: Some(false),
+            r#ref: None,
+            ..meta.clone()
+        };
+        let inner_codec = codec_type_for(&inner, &inner_meta, false, false)?;
+        return Ok(quote! {
+            fory_core::serializer::codec::OptionCodec<#inner, #inner_codec, #track_ref>
+        });
+    }
+
+    if let Some((name, Some(args))) = type_name_and_args(ty) {
+        if name == "Vec" {
+            if meta.encoding.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "encoding is only valid on integer values; use list(element(encoding = ...)) for Vec elements",
+                ));
+            }
+            if meta.map.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "map(...) config is only valid for HashMap fields",
+                ));
+            }
+            let elem_ty = single_type_arg(args, ty, "Vec")?;
+            if meta.list.is_none() && is_primitive_vec_type(ty) {
+                return Ok(quote! {
+                    fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref>
+                });
+            }
+            if meta.list.is_none() {
+                let elem_meta = ForyFieldMeta::default();
+                let elem_class = classify_field_type(elem_ty);
+                let elem_codec = codec_type_for(
+                    elem_ty,
+                    &elem_meta,
+                    elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+                    elem_meta.effective_ref(elem_class),
+                )?;
+                if contains_custom_trait_object(elem_ty) {
+                    return Ok(quote! {
+                        fory_core::serializer::codec::VecCodec<#elem_ty, #elem_codec, #nullable, #track_ref>
+                    });
+                }
+                return Ok(quote! {
+                    fory_core::serializer::codec::CollectionSerializerCodec<
+                        #ty,
+                        #elem_ty,
+                        #elem_codec,
+                        { fory_core::type_id::TypeId::LIST as u8 },
+                        #nullable,
+                        #track_ref
+                    >
+                });
+            }
+            let elem_meta = meta.element_meta();
+            let elem_class = classify_field_type(elem_ty);
+            let elem_nullable = elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty);
+            let elem_track_ref = elem_meta.effective_ref(elem_class);
+            let elem_codec = codec_type_for(elem_ty, &elem_meta, elem_nullable, elem_track_ref)?;
+            return Ok(quote! {
+                fory_core::serializer::codec::VecCodec<#elem_ty, #elem_codec, #nullable, #track_ref>
+            });
+        }
+        if name == "HashMap" {
+            if meta.encoding.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "encoding is only valid on integer values; use map(key(...), value(...)) for HashMap entries",
+                ));
+            }
+            if meta.list.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "list(...) config is only valid for Vec fields",
+                ));
+            }
+            let (key_ty, value_ty) = two_type_args(args, ty, "HashMap")?;
+            if meta.map.is_none() {
+                let key_meta = ForyFieldMeta::default();
+                let value_meta = ForyFieldMeta::default();
+                let key_class = classify_field_type(key_ty);
+                let value_class = classify_field_type(value_ty);
+                let key_codec = codec_type_for(
+                    key_ty,
+                    &key_meta,
+                    key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+                    key_meta.effective_ref(key_class),
+                )?;
+                let value_codec = codec_type_for(
+                    value_ty,
+                    &value_meta,
+                    value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+                    value_meta.effective_ref(value_class),
+                )?;
+                if contains_custom_trait_object(key_ty)
+                    || contains_custom_trait_object(value_ty)
+                    || contains_exact_any_object(key_ty)
+                    || contains_exact_any_object(value_ty)
+                {
+                    return Ok(quote! {
+                        fory_core::serializer::codec::HashMapCodec<#key_ty, #value_ty, #key_codec, #value_codec, #nullable, #track_ref>
+                    });
+                }
+                return Ok(quote! {
+                    fory_core::serializer::codec::MapSerializerCodec<
+                        #ty,
+                        #key_ty,
+                        #value_ty,
+                        #key_codec,
+                        #value_codec,
+                        #nullable,
+                        #track_ref
+                    >
+                });
+            }
+            let key_meta = meta.map_key_meta();
+            let value_meta = meta.map_value_meta();
+            let key_class = classify_field_type(key_ty);
+            let value_class = classify_field_type(value_ty);
+            let key_codec = codec_type_for(
+                key_ty,
+                &key_meta,
+                key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+                key_meta.effective_ref(key_class),
+            )?;
+            let value_codec = codec_type_for(
+                value_ty,
+                &value_meta,
+                value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+                value_meta.effective_ref(value_class),
+            )?;
+            return Ok(quote! {
+                fory_core::serializer::codec::HashMapCodec<#key_ty, #value_ty, #key_codec, #value_codec, #nullable, #track_ref>
+            });
+        }
+        if name == "HashSet" {
+            if meta.encoding.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "encoding is only valid on integer values",
+                ));
+            }
+            if meta.list.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "list(...) config is only valid for Vec fields",
+                ));
+            }
+            if meta.map.is_some() {
+                return Err(syn::Error::new_spanned(
+                    ty,
+                    "map(...) config is only valid for HashMap fields",
+                ));
+            }
+            let elem_ty = single_type_arg(args, ty, "HashSet")?;
+            let elem_meta = ForyFieldMeta::default();
+            let elem_class = classify_field_type(elem_ty);
+            let elem_codec = codec_type_for(
+                elem_ty,
+                &elem_meta,
+                elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+                elem_meta.effective_ref(elem_class),
+            )?;
+            return Ok(quote! {
+                fory_core::serializer::codec::CollectionSerializerCodec<
+                    #ty,
+                    #elem_ty,
+                    #elem_codec,
+                    { fory_core::type_id::TypeId::SET as u8 },
+                    #nullable,
+                    #track_ref
+                >
+            });
+        }
+        if is_serializer_backed_collection(&name) {
+            validate_serializer_backed_collection_meta(ty, meta, &name)?;
+            let elem_ty = single_type_arg(args, ty, &name)?;
+            let elem_meta = ForyFieldMeta::default();
+            let elem_class = classify_field_type(elem_ty);
+            let elem_codec = codec_type_for(
+                elem_ty,
+                &elem_meta,
+                elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+                elem_meta.effective_ref(elem_class),
+            )?;
+            let type_id = serializer_backed_collection_type_id(&name);
+            return Ok(quote! {
+                fory_core::serializer::codec::CollectionSerializerCodec<
+                    #ty,
+                    #elem_ty,
+                    #elem_codec,
+                    #type_id,
+                    #nullable,
+                    #track_ref
+                >
+            });
+        }
+        if is_serializer_backed_map(&name) {
+            validate_serializer_backed_map_meta(ty, meta, &name)?;
+            let (key_ty, value_ty) = two_type_args(args, ty, &name)?;
+            let key_meta = ForyFieldMeta::default();
+            let value_meta = ForyFieldMeta::default();
+            let key_class = classify_field_type(key_ty);
+            let value_class = classify_field_type(value_ty);
+            let key_codec = codec_type_for(
+                key_ty,
+                &key_meta,
+                key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+                key_meta.effective_ref(key_class),
+            )?;
+            let value_codec = codec_type_for(
+                value_ty,
+                &value_meta,
+                value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+                value_meta.effective_ref(value_class),
+            )?;
+            return Ok(quote! {
+                fory_core::serializer::codec::MapSerializerCodec<
+                    #ty,
+                    #key_ty,
+                    #value_ty,
+                    #key_codec,
+                    #value_codec,
+                    #nullable,
+                    #track_ref
+                >
+            });
+        }
+    }
+
+    if let Type::Array(array) = ty {
+        if meta.encoding.is_some() {
+            return Err(syn::Error::new_spanned(
+                ty,
+                "encoding is only valid on integer values",
+            ));
+        }
+        if meta.list.is_some() {
+            return Err(syn::Error::new_spanned(
+                ty,
+                "list(...) config is only valid for Vec fields",
+            ));
+        }
+        if meta.map.is_some() {
+            return Err(syn::Error::new_spanned(
+                ty,
+                "map(...) config is only valid for HashMap fields",
+            ));
+        }
+        if !is_primitive_array_type(ty) {
+            let elem_ty = array.elem.as_ref();
+            let elem_meta = ForyFieldMeta::default();
+            let elem_class = classify_field_type(elem_ty);
+            let elem_codec = codec_type_for(
+                elem_ty,
+                &elem_meta,
+                elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+                elem_meta.effective_ref(elem_class),
+            )?;
+            return Ok(quote! {
+                fory_core::serializer::codec::CollectionSerializerCodec<
+                    #ty,
+                    #elem_ty,
+                    #elem_codec,
+                    { fory_core::type_id::TypeId::LIST as u8 },
+                    #nullable,
+                    #track_ref
+                >
+            });
+        }
+    }
+
+    if is_exact_any(ty, "Box") {
+        return Ok(quote! { fory_core::serializer::codec::AnyBoxCodec<#nullable, #track_ref> });
+    }
+    if is_exact_any(ty, "Rc") {
+        return Ok(quote! { fory_core::serializer::codec::AnyRcCodec<#nullable, #track_ref> });
+    }
+    if is_exact_any(ty, "Arc") {
+        return Ok(quote! { fory_core::serializer::codec::AnyArcCodec<#nullable, #track_ref> });
+    }
+
+    if let Some((_, trait_name)) = is_box_dyn_trait(ty) {
+        let codec_ident = format_ident!("{}BoxCodec", trait_name);
+        return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+    }
+    if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
+        let codec_ident = format_ident!("{}RcCodec", trait_name);
+        return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+    }
+    if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
+        let codec_ident = format_ident!("{}ArcCodec", trait_name);
+        return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+    }
+
+    if meta.list.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            "list(...) config is only valid for Vec fields",
+        ));
+    }
+    if meta.map.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            "map(...) config is only valid for HashMap fields",
+        ));
+    }
+
+    if let Some(codec) = integer_codec_type(ty, meta, nullable, track_ref) {
+        return Ok(codec);
+    }
+
+    Ok(quote! { fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref> })
+}
+
+fn integer_codec_type(
+    ty: &Type,
+    meta: &ForyFieldMeta,
+    nullable: bool,
+    track_ref: bool,
+) -> Option<TokenStream> {
+    let type_name = ty.to_token_stream().to_string().replace(' ', "");
+    let encoding = meta.encoding.unwrap_or(IntEncoding::Varint);
+    match type_name.as_str() {
+        "i32" => {
+            let wire = match encoding {
+                IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::INT32 as u8 } },
+                IntEncoding::Varint => {
+                    quote! { { fory_core::type_id::TypeId::VARINT32 as u8 } }
+                }
+                IntEncoding::Tagged => {
+                    return Some(quote! {
+                        compile_error!("encoding = tagged is only valid for 64-bit integer fields")
+                    });
+                }
+            };
+            Some(quote! { fory_core::serializer::codec::I32Codec<#wire, #nullable, #track_ref> })
+        }
+        "i64" => {
+            let wire = match encoding {
+                IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::INT64 as u8 } },
+                IntEncoding::Varint => {
+                    quote! { { fory_core::type_id::TypeId::VARINT64 as u8 } }
+                }
+                IntEncoding::Tagged => {
+                    quote! { { fory_core::type_id::TypeId::TAGGED_INT64 as u8 } }
+                }
+            };
+            Some(quote! { fory_core::serializer::codec::I64Codec<#wire, #nullable, #track_ref> })
+        }
+        "u32" => {
+            let wire = match encoding {
+                IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::UINT32 as u8 } },
+                IntEncoding::Varint => {
+                    quote! { { fory_core::type_id::TypeId::VAR_UINT32 as u8 } }
+                }
+                IntEncoding::Tagged => {
+                    return Some(quote! {
+                        compile_error!("encoding = tagged is only valid for 64-bit integer fields")
+                    });
+                }
+            };
+            Some(quote! { fory_core::serializer::codec::U32Codec<#wire, #nullable, #track_ref> })
+        }
+        "u64" => {
+            let wire = match encoding {
+                IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::UINT64 as u8 } },
+                IntEncoding::Varint => {
+                    quote! { { fory_core::type_id::TypeId::VAR_UINT64 as u8 } }
+                }
+                IntEncoding::Tagged => {
+                    quote! { { fory_core::type_id::TypeId::TAGGED_UINT64 as u8 } }
+                }
+            };
+            Some(quote! { fory_core::serializer::codec::U64Codec<#wire, #nullable, #track_ref> })
+        }
+        _ => {
+            if meta.encoding.is_some() {
+                Some(quote! {
+                    compile_error!("encoding is only valid for i32, i64, u32, and u64 fields")
+                })
+            } else {
+                None
+            }
+        }
+    }
+}
+
+fn type_name_and_args(
+    ty: &Type,
+) -> Option<(
+    String,
+    Option<&syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>>,
+)> {
+    let Type::Path(type_path) = ty else {
+        return None;
+    };
+    let seg = type_path.path.segments.last()?;
+    let PathArguments::AngleBracketed(args) = &seg.arguments else {
+        return Some((seg.ident.to_string(), None));
+    };
+    Some((seg.ident.to_string(), Some(&args.args)))
+}
+
+fn single_type_arg<'a>(
+    args: &'a syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+    ty: &Type,
+    owner: &str,
+) -> syn::Result<&'a Type> {
+    args.iter()
+        .find_map(|arg| match arg {
+            GenericArgument::Type(ty) => Some(ty),
+            _ => None,
+        })
+        .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires one type argument")))
+}
+
+fn two_type_args<'a>(
+    args: &'a syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+    ty: &Type,
+    owner: &str,
+) -> syn::Result<(&'a Type, &'a Type)> {
+    let mut iter = args.iter().filter_map(|arg| match arg {
+        GenericArgument::Type(ty) => Some(ty),
+        _ => None,
+    });
+    let first = iter
+        .next()
+        .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires key type")))?;
+    let second = iter
+        .next()
+        .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires value type")))?;
+    Ok((first, second))
+}
+
+fn is_container_type(ty: &Type) -> bool {
+    if matches!(ty, Type::Array(_) | Type::Tuple(_)) {
+        return true;
+    }
+    let Some((name, _)) = type_name_and_args(ty) else {
+        return false;
+    };
+    matches!(
+        name.as_str(),
+        "Vec"
+            | "VecDeque"
+            | "LinkedList"
+            | "HashSet"
+            | "BTreeSet"
+            | "BinaryHeap"
+            | "HashMap"
+            | "BTreeMap"
+    )
+}
+
+fn contains_custom_trait_object(ty: &Type) -> bool {
+    if !is_exact_any(ty, "Box") && is_box_dyn_trait(ty).is_some() {
+        return true;
+    }
+    if is_rc_dyn_trait(ty).is_some() || is_arc_dyn_trait(ty).is_some() {
+        return true;
+    }
+    if let Some(inner) = extract_option_inner_type(ty) {
+        return contains_custom_trait_object(&inner);
+    }
+    if let Type::Array(array) = ty {
+        return contains_custom_trait_object(array.elem.as_ref());
+    }
+    let Some((_, Some(args))) = type_name_and_args(ty) else {
+        return false;
+    };
+    args.iter().any(|arg| {
+        if let GenericArgument::Type(ty) = arg {
+            contains_custom_trait_object(ty)
+        } else {
+            false
+        }
+    })
+}
+
+fn contains_exact_any_object(ty: &Type) -> bool {
+    if is_exact_any(ty, "Box") || is_exact_any(ty, "Rc") || is_exact_any(ty, "Arc") {
+        return true;
+    }
+    if let Some(inner) = extract_option_inner_type(ty) {
+        return contains_exact_any_object(&inner);
+    }
+    if let Type::Array(array) = ty {
+        return contains_exact_any_object(array.elem.as_ref());
+    }
+    let Some((_, Some(args))) = type_name_and_args(ty) else {
+        return false;
+    };
+    args.iter().any(|arg| {
+        if let GenericArgument::Type(ty) = arg {
+            contains_exact_any_object(ty)
+        } else {
+            false
+        }
+    })
+}
+
+fn field_type_expr_for(ty: &Type, nullable: bool, track_ref: bool) -> syn::Result<TokenStream> {
+    if let Some(inner) = extract_option_inner_type(ty) {
+        return field_type_expr_for(&inner, true, track_ref);
+    }
+
+    if let Type::Array(array) = ty {
+        let type_id = get_type_id_by_type_ast(ty);
+        if fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&type_id) {
+            return Ok(field_type_literal(type_id, nullable, track_ref, Vec::new()));
+        }
+        let elem_ty = array.elem.as_ref();
+        let elem_type = nested_field_type_expr(elem_ty)?;
+        return Ok(field_type_literal(
+            fory_core::type_id::TypeId::LIST as u32,
+            nullable,
+            track_ref,
+            vec![elem_type],
+        ));
+    }
+
+    if matches!(ty, Type::Tuple(_)) {
+        return Ok(field_type_literal(
+            fory_core::type_id::TypeId::LIST as u32,
+            nullable,
+            track_ref,
+            vec![quote! {
+                fory_core::meta::FieldType::new(
+                    fory_core::type_id::TypeId::UNKNOWN as u32,
+                    true,
+                    Vec::new(),
+                )
+            }],
+        ));
+    }
+
+    if let Some((name, Some(args))) = type_name_and_args(ty) {
+        match name.as_str() {
+            "Vec" => {
+                let type_id = get_type_id_by_type_ast(ty);
+                if fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&type_id) {
+                    return Ok(field_type_literal(type_id, nullable, track_ref, Vec::new()));
+                }
+                let elem_ty = single_type_arg(args, ty, "Vec")?;
+                let elem_type = nested_field_type_expr(elem_ty)?;
+                return Ok(field_type_literal(
+                    fory_core::type_id::TypeId::LIST as u32,
+                    nullable,
+                    track_ref,
+                    vec![elem_type],
+                ));
+            }
+            "VecDeque" | "LinkedList" => {
+                let elem_ty = single_type_arg(args, ty, &name)?;
+                let elem_type = nested_field_type_expr(elem_ty)?;
+                return Ok(field_type_literal(
+                    fory_core::type_id::TypeId::LIST as u32,
+                    nullable,
+                    track_ref,
+                    vec![elem_type],
+                ));
+            }
+            "HashSet" | "BTreeSet" | "BinaryHeap" => {
+                let elem_ty = single_type_arg(args, ty, &name)?;
+                let elem_type = nested_field_type_expr(elem_ty)?;
+                return Ok(field_type_literal(
+                    fory_core::type_id::TypeId::SET as u32,
+                    nullable,
+                    track_ref,
+                    vec![elem_type],
+                ));
+            }
+            "HashMap" | "BTreeMap" => {
+                let (key_ty, value_ty) = two_type_args(args, ty, &name)?;
+                let key_type = nested_field_type_expr(key_ty)?;
+                let value_type = nested_field_type_expr(value_ty)?;
+                return Ok(field_type_literal(
+                    fory_core::type_id::TypeId::MAP as u32,
+                    nullable,
+                    track_ref,
+                    vec![key_type, value_type],
+                ));
+            }
+            _ => {}
+        }
+    }
+
+    Ok(serializer_field_type_expr(ty, nullable, track_ref))
+}
+
+fn nested_field_type_expr(ty: &Type) -> syn::Result<TokenStream> {
+    let meta = ForyFieldMeta::default();
+    let class = classify_field_type(ty);
+    let nullable = meta.effective_nullable(class) || is_option_type(ty);
+    let track_ref = meta.effective_ref(class);
+    let codec_ty = codec_type_for(ty, &meta, nullable, track_ref)?;
+    Ok(quote! {
+        <#codec_ty as fory_core::serializer::codec::Codec<#ty>>::field_type(type_resolver)?
+    })
+}
+
+fn field_type_literal(
+    type_id: u32,
+    nullable: bool,
+    track_ref: bool,
+    generics: Vec<TokenStream>,
+) -> TokenStream {
+    quote! {
+        fory_core::meta::FieldType::new_with_ref(
+            #type_id,
+            #nullable,
+            #track_ref,
+            vec![#(#generics),*],
+        )
+    }
+}
+
+fn serializer_field_type_expr(ty: &Type, nullable: bool, track_ref: bool) -> TokenStream {
+    quote! {
+        <fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref>
+            as fory_core::serializer::codec::Codec<#ty>>::field_type(type_resolver)?
+    }
+}
+
+fn serializer_ref_mode_for_field(field: &syn::Field) -> TokenStream {
+    let (nullable, track_ref) = serializer_field_markers(field);
+    if track_ref {
+        quote! { fory_core::RefMode::Tracking }
+    } else if nullable {
+        quote! { fory_core::RefMode::NullOnly }
+    } else {
+        quote! { fory_core::RefMode::None }
+    }
+}
+
+fn serializer_field_can_use_data_path(field: &syn::Field) -> bool {
+    let (nullable, track_ref) = serializer_field_markers(field);
+    !nullable && !track_ref
+}
+
+fn serializer_field_markers(field: &syn::Field) -> (bool, bool) {
+    let meta = parse_field_meta(field).unwrap_or_default();
+    let type_class = classify_field_type(&field.ty);
+    let nullable = meta.effective_nullable(type_class) || is_option_type(&field.ty);
+    let track_ref = meta.effective_ref(type_class);
+    (nullable, track_ref)
+}
+
+fn is_serializer_backed_collection(name: &str) -> bool {
+    matches!(name, "VecDeque" | "LinkedList" | "BTreeSet" | "BinaryHeap")
+}
+
+fn serializer_backed_collection_type_id(name: &str) -> TokenStream {
+    match name {
+        "BTreeSet" | "BinaryHeap" => quote! { { fory_core::type_id::TypeId::SET as u8 } },
+        _ => quote! { { fory_core::type_id::TypeId::LIST as u8 } },
+    }
+}
+
+fn validate_serializer_backed_collection_meta(
+    ty: &Type,
+    meta: &ForyFieldMeta,
+    name: &str,
+) -> syn::Result<()> {
+    if meta.encoding.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            "encoding is only valid on integer values",
+        ));
+    }
+    if meta.list.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            format!("list(...) config is only valid for Vec fields, not {name}"),
+        ));
+    }
+    if meta.map.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            format!("map(...) config is only valid for map fields, not {name}"),
+        ));
+    }
+    Ok(())
+}
+
+fn is_serializer_backed_map(name: &str) -> bool {
+    matches!(name, "BTreeMap")
+}
+
+fn validate_serializer_backed_map_meta(
+    ty: &Type,
+    meta: &ForyFieldMeta,
+    name: &str,
+) -> syn::Result<()> {
+    if meta.encoding.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            "encoding is only valid on integer values; use map(key(...), value(...)) for map entries",
+        ));
+    }
+    if meta.list.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            format!("list(...) config is only valid for list fields, not {name}"),
+        ));
+    }
+    if meta.map.is_some() {
+        return Err(syn::Error::new_spanned(
+            ty,
+            format!("map(...) config is currently supported only for HashMap fields, not {name}"),
+        ));
+    }
+    Ok(())
+}
+
+fn is_exact_any(ty: &Type, owner: &str) -> bool {
+    let Some((name, Some(args))) = type_name_and_args(ty) else {
+        return false;
+    };
+    if name != owner {
+        return false;
+    }
+    let Some(GenericArgument::Type(Type::TraitObject(trait_obj))) = args.first() else {
+        return false;
+    };
+    trait_obj.bounds.iter().any(|bound| {
+        if let syn::TypeParamBound::Trait(trait_bound) = bound {
+            trait_bound
+                .path
+                .segments
+                .last()
+                .is_some_and(|seg| seg.ident == "Any")
+        } else {
+            false
+        }
+    })
+}
+
+fn is_primitive_vec_type(ty: &Type) -> bool {
+    fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&get_type_id_by_type_ast(ty))
+}
+
+fn is_primitive_array_type(ty: &Type) -> bool {
+    fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&get_type_id_by_type_ast(ty))
+}
+
+pub(crate) fn default_expr_for_type(ty: &Type) -> TokenStream {
+    if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
+        let wrapper_ty = format_ident!("{}Rc", trait_name);
+        let trait_ident = format_ident!("{}", trait_name);
+        return quote! {
+            {
+                let wrapper = <#wrapper_ty as fory_core::ForyDefault>::fory_default();
+                std::rc::Rc::<dyn #trait_ident>::from(wrapper)
+            }
+        };
+    }
+    if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
+        let wrapper_ty = format_ident!("{}Arc", trait_name);
+        let trait_ident = format_ident!("{}", trait_name);
+        return quote! {
+            {
+                let wrapper = <#wrapper_ty as fory_core::ForyDefault>::fory_default();
+                std::sync::Arc::<dyn #trait_ident>::from(wrapper)
+            }
+        };
+    }
+    quote! { <#ty as fory_core::ForyDefault>::fory_default() }
+}
diff --git a/rust/fory-derive/src/object/field_meta.rs b/rust/fory-derive/src/object/field_meta.rs
index e50112efa..626153abc 100644
--- a/rust/fory-derive/src/object/field_meta.rs
+++ b/rust/fory-derive/src/object/field_meta.rs
@@ -22,16 +22,13 @@
 //! - `nullable`: Whether the field can be null (default: false, except Option/RcWeak/ArcWeak)
 //! - `ref`: Whether to enable reference tracking (default: false, except Rc/Arc/RcWeak/ArcWeak)
 //! - `skip`: Skip this field during serialization
-//! - `compress`: For i32/u32 fields: true (VARINT32/VAR_UINT32) or false (INT32/UINT32 fixed)
-//! - `encoding`: For i32/u32: "varint", "fixed"; for u64: "varint", "fixed", "tagged"
-//! - `type_id`: Explicit type ID override (e.g., "int8_array", "uint8_array")
-//!
-//! Both `compress` and `encoding` are converted to a `type_id` internally. If both are
-//! specified, they must not conflict.
+//! - `encoding`: Integer wire encoding, one of `varint`, `fixed`, or `tagged`
+//! - `list(element(...))`: Nested list element configuration
+//! - `map(key(...), value(...))`: Nested map key/value configuration
 
-use fory_core::type_id::TypeId;
 use quote::ToTokens;
 use std::collections::HashMap;
+use syn::spanned::Spanned;
 use syn::{Field, GenericArgument, PathArguments, Type};
 
 /// Represents parsed `#[fory(...)]` field attributes
@@ -45,9 +42,31 @@ pub struct ForyFieldMeta {
     pub r#ref: Option<bool>,
     /// Whether to skip this field entirely
     pub skip: bool,
-    /// Explicit type ID for encoding or array type overrides.
-    /// This is set by `compress`, `encoding`, or `type_id` attributes.
-    pub type_id: Option<i16>,
+    /// Integer wire encoding selected by semantic field config.
+    pub encoding: Option<IntEncoding>,
+    /// Nested list element configuration.
+    pub list: Option<ForyListMeta>,
+    /// Nested map key/value configuration.
+    pub map: Option<ForyMapMeta>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ForyListMeta {
+    pub element: Option<Box<ForyFieldMeta>>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ForyMapMeta {
+    pub key: Option<Box<ForyFieldMeta>>,
+    pub value: Option<Box<ForyFieldMeta>>,
+}
+
+/// Integer wire encoding selected by `#[fory(encoding = ...)]`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum IntEncoding {
+    Varint,
+    Fixed,
+    Tagged,
 }
 
 /// Type classification for determining default nullable/ref behavior
@@ -108,154 +127,218 @@ impl ForyFieldMeta {
     pub fn uses_tag_id(&self) -> bool {
         self.id.is_some_and(|id| id >= 0)
     }
-}
 
-/// Encoding specified via `compress` attribute
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum CompressEncoding {
-    Varint,
-    Fixed,
-}
+    pub fn element_meta(&self) -> ForyFieldMeta {
+        self.list
+            .as_ref()
+            .and_then(|list| list.element.as_ref())
+            .map(|meta| (**meta).clone())
+            .unwrap_or_default()
+    }
 
-/// Encoding specified via `encoding` attribute
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ExplicitEncoding {
-    Varint,
-    Fixed,
-    Tagged,
+    pub fn map_key_meta(&self) -> ForyFieldMeta {
+        self.map
+            .as_ref()
+            .and_then(|map| map.key.as_ref())
+            .map(|meta| (**meta).clone())
+            .unwrap_or_default()
+    }
+
+    pub fn map_value_meta(&self) -> ForyFieldMeta {
+        self.map
+            .as_ref()
+            .and_then(|map| map.value.as_ref())
+            .map(|meta| (**meta).clone())
+            .unwrap_or_default()
+    }
 }
 
 /// Parse `#[fory(...)]` attributes from a field
 pub fn parse_field_meta(field: &Field) -> syn::Result<ForyFieldMeta> {
     let mut meta = ForyFieldMeta::default();
-    let mut compress_encoding: Option<CompressEncoding> = None;
-    let mut explicit_encoding: Option<ExplicitEncoding> = None;
-    let mut explicit_type_id: Option<i16> = None;
 
     for attr in &field.attrs {
         if !attr.path().is_ident("fory") {
             continue;
         }
 
-        attr.parse_nested_meta(|nested| {
-            if nested.path.is_ident("id") {
-                let lit: syn::LitInt = nested.value()?.parse()?;
-                let id: i32 = lit.base10_parse()?;
-                if id < -1 {
-                    return Err(syn::Error::new(lit.span(), "id must be >= -1"));
-                }
-                meta.id = Some(id);
-            } else if nested.path.is_ident("nullable") {
-                let value = parse_bool_or_flag(&nested)?;
-                meta.nullable = Some(value);
-            } else if nested.path.is_ident("ref") {
-                let value = parse_bool_or_flag(&nested)?;
-                meta.r#ref = Some(value);
-            } else if nested.path.is_ident("skip") {
-                meta.skip = true;
-            } else if nested.path.is_ident("compress") {
-                let value = parse_bool_or_flag(&nested)?;
-                compress_encoding = Some(if value {
-                    CompressEncoding::Varint
-                } else {
-                    CompressEncoding::Fixed
-                });
-            } else if nested.path.is_ident("encoding") {
-                let lit: syn::LitStr = nested.value()?.parse()?;
-                let encoding_str = lit.value();
-                explicit_encoding = Some(match encoding_str.as_str() {
-                    "varint" => ExplicitEncoding::Varint,
-                    "fixed" => ExplicitEncoding::Fixed,
-                    "tagged" => ExplicitEncoding::Tagged,
-                    _ => {
-                        return Err(syn::Error::new(
-                            lit.span(),
-                            "encoding must be \"varint\", \"fixed\", or \"tagged\"",
-                        ));
-                    }
-                });
-            } else if nested.path.is_ident("type_id") {
-                let lit: syn::LitStr = nested.value()?.parse()?;
-                explicit_type_id = Some(parse_type_id_tag(&lit)?);
-            }
-            Ok(())
-        })?;
+        attr.parse_nested_meta(|nested| parse_meta_item(&mut meta, nested, true))?;
     }
 
-    if explicit_type_id.is_some() && (compress_encoding.is_some() || explicit_encoding.is_some()) {
-        return Err(syn::Error::new_spanned(
-            field,
-            "type_id cannot be combined with compress or encoding attributes",
-        ));
-    }
+    Ok(meta)
+}
 
-    // Validate that compress and encoding don't conflict if both are specified
-    if let (Some(compress), Some(explicit)) = (compress_encoding, explicit_encoding) {
-        let compress_implies = match compress {
-            CompressEncoding::Varint => ExplicitEncoding::Varint,
-            CompressEncoding::Fixed => ExplicitEncoding::Fixed,
-        };
-        // Only check conflict for varint/fixed (tagged is only for u64)
-        if explicit != ExplicitEncoding::Tagged && compress_implies != explicit {
-            let compress_str = match compress {
-                CompressEncoding::Varint => "true",
-                CompressEncoding::Fixed => "false",
-            };
-            let encoding_str = match explicit {
-                ExplicitEncoding::Varint => "varint",
-                ExplicitEncoding::Fixed => "fixed",
-                ExplicitEncoding::Tagged => "tagged",
-            };
-            return Err(syn::Error::new_spanned(
-                field,
-                format!(
-                    "conflicting attributes: compress={} implies {} encoding, but encoding=\"{}\" was specified",
-                    compress_str,
-                    match compress {
-                        CompressEncoding::Varint => "varint",
-                        CompressEncoding::Fixed => "fixed",
-                    },
-                    encoding_str
-                ),
+fn parse_meta_item(
+    meta: &mut ForyFieldMeta,
+    nested: syn::meta::ParseNestedMeta,
+    allow_field_keys: bool,
+) -> syn::Result<()> {
+    if nested.path.is_ident("id") {
+        if !allow_field_keys {
+            return Err(syn::Error::new(
+                nested.path.span(),
+                "id is only valid on a struct field, not inside nested list/map config",
             ));
         }
+        let lit: syn::LitInt = nested.value()?.parse()?;
+        let id: i32 = lit.base10_parse()?;
+        if id < -1 {
+            return Err(syn::Error::new(lit.span(), "id must be >= -1"));
+        }
+        if meta.id.is_some() {
+            return Err(syn::Error::new(nested.path.span(), "duplicate id config"));
+        }
+        meta.id = Some(id);
+    } else if nested.path.is_ident("nullable") {
+        let value = parse_bool_or_flag(&nested)?;
+        if meta.nullable.is_some() {
+            return Err(syn::Error::new(
+                nested.path.span(),
+                "duplicate nullable config",
+            ));
+        }
+        meta.nullable = Some(value);
+    } else if nested.path.is_ident("ref") {
+        let value = parse_bool_or_flag(&nested)?;
+        if meta.r#ref.is_some() {
+            return Err(syn::Error::new(nested.path.span(), "duplicate ref config"));
+        }
+        meta.r#ref = Some(value);
+    } else if nested.path.is_ident("skip") {
+        if !allow_field_keys {
+            return Err(syn::Error::new(
+                nested.path.span(),
+                "skip is only valid on a struct field, not inside nested list/map config",
+            ));
+        }
+        if meta.skip {
+            return Err(syn::Error::new(nested.path.span(), "duplicate skip config"));
+        }
+        meta.skip = true;
+    } else if nested.path.is_ident("compress") {
+        return Err(syn::Error::new(
+            nested.path.span(),
+            "compress has been removed; use encoding = varint or encoding = fixed",
+        ));
+    } else if nested.path.is_ident("encoding") {
+        let encoding = parse_encoding_value(&nested)?;
+        if meta.encoding.is_some() {
+            return Err(syn::Error::new(
+                nested.path.span(),
+                "duplicate encoding config",
+            ));
+        }
+        meta.encoding = Some(encoding);
+    } else if nested.path.is_ident("list") {
+        if meta.list.is_some() {
+            return Err(syn::Error::new(nested.path.span(), "duplicate list config"));
+        }
+        parse_list_meta(meta, nested)?;
+    } else if nested.path.is_ident("map") {
+        if meta.map.is_some() {
+            return Err(syn::Error::new(nested.path.span(), "duplicate map config"));
+        }
+        parse_map_meta(meta, nested)?;
+    } else if nested.path.is_ident("type_id") {
+        return Err(syn::Error::new(
+            nested.path.span(),
+            "type_id is internal wire metadata and is not a field attribute",
+        ));
+    } else {
+        return Err(syn::Error::new(
+            nested.path.span(),
+            "unknown fory field attribute",
+        ));
     }
+    Ok(())
+}
 
-    if let Some(type_id) = explicit_type_id {
-        meta.type_id = Some(type_id);
-        return Ok(meta);
-    }
+fn parse_list_meta(
+    meta: &mut ForyFieldMeta,
+    nested: syn::meta::ParseNestedMeta,
+) -> syn::Result<()> {
+    let mut list_meta = meta.list.clone().unwrap_or_default();
+    nested.parse_nested_meta(|item| {
+        if item.path.is_ident("element") {
+            if list_meta.element.is_some() {
+                return Err(syn::Error::new(
+                    item.path.span(),
+                    "duplicate list element config",
+                ));
+            }
+            let mut element = list_meta
+                .element
+                .take()
+                .map(|meta| *meta)
+                .unwrap_or_default();
+            item.parse_nested_meta(|child| parse_meta_item(&mut element, child, false))?;
+            list_meta.element = Some(Box::new(element));
+            Ok(())
+        } else {
+            Err(syn::Error::new(
+                item.path.span(),
+                "list config supports only element(...)",
+            ))
+        }
+    })?;
+    meta.list = Some(list_meta);
+    Ok(())
+}
 
-    // Convert encoding to type_id
-    // Priority: explicit_encoding > compress_encoding
-    // Note: The actual type_id depends on the field type (i32, u32, u64), but we store
-    // a "canonical" type_id here. The util.rs code will interpret it correctly.
-    if let Some(explicit) = explicit_encoding {
-        meta.type_id = Some(match explicit {
-            // For varint, we use the signed variant as canonical; util.rs adjusts for unsigned
-            ExplicitEncoding::Varint => TypeId::VARINT32 as i16,
-            ExplicitEncoding::Fixed => TypeId::INT32 as i16,
-            ExplicitEncoding::Tagged => TypeId::TAGGED_UINT64 as i16,
-        });
-    } else if let Some(compress) = compress_encoding {
-        meta.type_id = Some(match compress {
-            CompressEncoding::Varint => TypeId::VARINT32 as i16,
-            CompressEncoding::Fixed => TypeId::INT32 as i16,
-        });
-    }
+fn parse_map_meta(meta: &mut ForyFieldMeta, nested: syn::meta::ParseNestedMeta) -> syn::Result<()> {
+    let mut map_meta = meta.map.clone().unwrap_or_default();
+    nested.parse_nested_meta(|item| {
+        if item.path.is_ident("key") {
+            if map_meta.key.is_some() {
+                return Err(syn::Error::new(
+                    item.path.span(),
+                    "duplicate map key config",
+                ));
+            }
+            let mut key = map_meta.key.take().map(|meta| *meta).unwrap_or_default();
+            item.parse_nested_meta(|child| parse_meta_item(&mut key, child, false))?;
+            map_meta.key = Some(Box::new(key));
+            Ok(())
+        } else if item.path.is_ident("value") {
+            if map_meta.value.is_some() {
+                return Err(syn::Error::new(
+                    item.path.span(),
+                    "duplicate map value config",
+                ));
+            }
+            let mut value = map_meta.value.take().map(|meta| *meta).unwrap_or_default();
+            item.parse_nested_meta(|child| parse_meta_item(&mut value, child, false))?;
+            map_meta.value = Some(Box::new(value));
+            Ok(())
+        } else {
+            Err(syn::Error::new(
+                item.path.span(),
+                "map config supports only key(...) and value(...)",
+            ))
+        }
+    })?;
+    meta.map = Some(map_meta);
+    Ok(())
+}
 
-    Ok(meta)
+fn parse_encoding_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<IntEncoding> {
+    let value = meta.value()?;
+    if value.peek(syn::LitStr) {
+        let lit: syn::LitStr = value.parse()?;
+        return parse_encoding_name(&lit.value(), lit.span());
+    }
+    let ident: syn::Ident = value.parse()?;
+    parse_encoding_name(&ident.to_string(), ident.span())
 }
 
-fn parse_type_id_tag(lit: &syn::LitStr) -> syn::Result<i16> {
-    let value = lit.value();
-    match value.as_str() {
-        "int8_array" => Ok(TypeId::INT8_ARRAY as i16),
-        "uint8_array" => Ok(TypeId::UINT8_ARRAY as i16),
-        "union" => Ok(TypeId::UNION as i16),
+fn parse_encoding_name(value: &str, span: proc_macro2::Span) -> syn::Result<IntEncoding> {
+    match value {
+        "varint" => Ok(IntEncoding::Varint),
+        "fixed" => Ok(IntEncoding::Fixed),
+        "tagged" => Ok(IntEncoding::Tagged),
         _ => Err(syn::Error::new(
-            lit.span(),
-            "type_id must be \"int8_array\", \"uint8_array\", or \"union\"",
+            span,
+            "encoding must be varint, fixed, or tagged",
         )),
     }
 }
@@ -583,7 +666,9 @@ mod tests {
             nullable: Some(true),
             r#ref: None,
             skip: false,
-            type_id: None,
+            encoding: None,
+            list: None,
+            map: None,
         };
         assert!(meta.effective_nullable(FieldTypeClass::Primitive)); // Would be false by default
 
@@ -593,157 +678,241 @@ mod tests {
             nullable: None,
             r#ref: Some(false),
             skip: false,
-            type_id: None,
+            encoding: None,
+            list: None,
+            map: None,
         };
         assert!(!meta.effective_ref(FieldTypeClass::Rc)); // Would be true by default
     }
 
     #[test]
-    fn test_parse_compress_attribute() {
-        // compress=false sets type_id to INT32 (fixed encoding)
+    fn test_parse_removed_compress_attribute() {
         let field: Field = parse_quote! {
             #[fory(compress = false)]
             value: u32
         };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
-
-        // compress=true sets type_id to VARINT32 (variable encoding)
-        let field: Field = parse_quote! {
-            #[fory(compress = true)]
-            value: u32
-        };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
-
-        // Standalone compress flag should set to varint
-        let field: Field = parse_quote! {
-            #[fory(compress)]
-            value: u32
-        };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("compress has been removed"));
     }
 
     #[test]
     fn test_parse_encoding_attribute() {
-        // encoding="varint" sets type_id to VARINT32
         let field: Field = parse_quote! {
-            #[fory(encoding = "varint")]
+            #[fory(encoding = varint)]
             value: u64
         };
         let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+        assert_eq!(meta.encoding, Some(IntEncoding::Varint));
 
-        // encoding="fixed" sets type_id to INT32
         let field: Field = parse_quote! {
-            #[fory(encoding = "fixed")]
+            #[fory(encoding = fixed)]
             value: u64
         };
         let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+        assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
 
-        // encoding="tagged" sets type_id to TAGGED_UINT64
         let field: Field = parse_quote! {
-            #[fory(encoding = "tagged")]
+            #[fory(encoding = tagged)]
             value: u64
         };
         let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::TAGGED_UINT64 as i16));
+        assert_eq!(meta.encoding, Some(IntEncoding::Tagged));
     }
 
     #[test]
     fn test_parse_encoding_for_i32_u32() {
         // encoding="varint" for i32/u32
         let field: Field = parse_quote! {
-            #[fory(encoding = "varint")]
+            #[fory(encoding = varint)]
             value: i32
         };
         let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+        assert_eq!(meta.encoding, Some(IntEncoding::Varint));
 
         // encoding="fixed" for i32/u32
         let field: Field = parse_quote! {
-            #[fory(encoding = "fixed")]
+            #[fory(encoding = fixed)]
             value: u32
         };
         let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+        assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
     }
 
     #[test]
-    fn test_compress_encoding_no_conflict() {
-        // compress=true with encoding="varint" - no conflict
+    fn test_removed_type_id_attribute() {
         let field: Field = parse_quote! {
-            #[fory(compress = true, encoding = "varint")]
+            #[fory(type_id = "union")]
             value: i32
         };
-        let meta = parse_field_meta(&field);
-        assert!(meta.is_ok());
-        let meta = meta.unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("type_id is internal wire metadata"));
+    }
 
-        // compress=false with encoding="fixed" - no conflict
+    #[test]
+    fn test_parse_combined_attributes() {
+        // nullable with encoding="tagged" (for u64)
         let field: Field = parse_quote! {
-            #[fory(compress = false, encoding = "fixed")]
-            value: u32
+            #[fory(nullable, encoding = tagged)]
+            value: Option<u64>
+        };
+        let meta = parse_field_meta(&field).unwrap();
+        assert_eq!(meta.nullable, Some(true));
+        assert_eq!(meta.encoding, Some(IntEncoding::Tagged));
+
+        // encoding="fixed" for Option<i32>
+        let field: Field = parse_quote! {
+            #[fory(nullable, encoding = fixed)]
+            value: Option<i32>
+        };
+        let meta = parse_field_meta(&field).unwrap();
+        assert_eq!(meta.nullable, Some(true));
+        assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
+    }
+
+    #[test]
+    fn test_parse_nested_list_and_map_config() {
+        let field: Field = parse_quote! {
+            #[fory(nullable, list(element(encoding = fixed)))]
+            values: Vec<Option<i32>>
+        };
+        let meta = parse_field_meta(&field).unwrap();
+        assert_eq!(meta.nullable, Some(true));
+        assert_eq!(
+            meta.list
+                .as_ref()
+                .and_then(|list| list.element.as_ref())
+                .and_then(|element| element.encoding),
+            Some(IntEncoding::Fixed)
+        );
+
+        let field: Field = parse_quote! {
+            #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))]
+            data: HashMap<Option<i32>, Option<u64>>
+        };
+        let meta = parse_field_meta(&field).unwrap();
+        let map = meta.map.as_ref().unwrap();
+        assert_eq!(
+            map.key.as_ref().and_then(|key| key.encoding),
+            Some(IntEncoding::Fixed)
+        );
+        assert_eq!(
+            map.value.as_ref().and_then(|value| value.nullable),
+            Some(true)
+        );
+        assert_eq!(
+            map.value.as_ref().and_then(|value| value.encoding),
+            Some(IntEncoding::Tagged)
+        );
+    }
+
+    #[test]
+    fn test_reject_field_only_keys_inside_nested_config() {
+        let field: Field = parse_quote! {
+            #[fory(list(element(id = 1)))]
+            values: Vec<i32>
         };
-        let meta = parse_field_meta(&field);
-        assert!(meta.is_ok());
-        let meta = meta.unwrap();
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("id is only valid on a struct field"));
     }
 
     #[test]
-    fn test_compress_encoding_conflict() {
-        // compress=true with encoding="fixed" - conflict!
+    fn test_reject_invalid_field_meta_grammar() {
         let field: Field = parse_quote! {
-            #[fory(compress = true, encoding = "fixed")]
+            #[fory(unknown = true)]
             value: i32
         };
-        let result = parse_field_meta(&field);
-        assert!(result.is_err());
-        let err = result.unwrap_err();
-        assert!(err.to_string().contains("conflicting"));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("unknown fory field attribute"));
 
-        // compress=false with encoding="varint" - conflict!
         let field: Field = parse_quote! {
-            #[fory(compress = false, encoding = "varint")]
-            value: u32
+            #[fory(encoding = zigzag)]
+            value: i64
         };
-        let result = parse_field_meta(&field);
-        assert!(result.is_err());
-        let err = result.unwrap_err();
-        assert!(err.to_string().contains("conflicting"));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("encoding must be varint, fixed, or tagged"));
+
+        let field: Field = parse_quote! {
+            #[fory(map(element(encoding = fixed)))]
+            data: HashMap<i32, i32>
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("map config supports only key(...) and value(...)"));
     }
 
     #[test]
-    fn test_parse_combined_attributes() {
-        // nullable with compress=false
+    fn test_reject_nested_field_only_and_wire_attrs() {
         let field: Field = parse_quote! {
-            #[fory(nullable, compress = false)]
-            value: Option<u32>
+            #[fory(map(value(skip)))]
+            data: HashMap<i32, i32>
         };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.nullable, Some(true));
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("skip is only valid on a struct field"));
 
-        // nullable with encoding="tagged" (for u64)
         let field: Field = parse_quote! {
-            #[fory(nullable, encoding = "tagged")]
-            value: Option<u64>
+            #[fory(map(key(type_id = 123)))]
+            data: HashMap<i32, i32>
         };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.nullable, Some(true));
-        assert_eq!(meta.type_id, Some(TypeId::TAGGED_UINT64 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("type_id is internal wire metadata"));
+    }
 
-        // encoding="fixed" for Option<i32>
+    #[test]
+    fn test_reject_duplicate_field_meta_config() {
         let field: Field = parse_quote! {
-            #[fory(nullable, encoding = "fixed")]
+            #[fory(id = 1, id = 2)]
+            value: i32
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate id config"));
+
+        let field: Field = parse_quote! {
+            #[fory(nullable, nullable = false)]
             value: Option<i32>
         };
-        let meta = parse_field_meta(&field).unwrap();
-        assert_eq!(meta.nullable, Some(true));
-        assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate nullable config"));
+
+        let field: Field = parse_quote! {
+            #[fory(encoding = fixed, encoding = varint)]
+            value: i32
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate encoding config"));
+    }
+
+    #[test]
+    fn test_reject_duplicate_nested_config() {
+        let field: Field = parse_quote! {
+            #[fory(list(element(encoding = fixed), element(nullable = true)))]
+            values: Vec<Option<i32>>
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate list element config"));
+
+        let field: Field = parse_quote! {
+            #[fory(map(key(encoding = fixed), key(nullable = true)))]
+            data: HashMap<Option<i32>, i32>
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate map key config"));
+
+        let field: Field = parse_quote! {
+            #[fory(map(value(encoding = fixed), value(nullable = true)))]
+            data: HashMap<i32, Option<i32>>
+        };
+        let err = parse_field_meta(&field).unwrap_err();
+        assert!(err.to_string().contains("duplicate map value config"));
     }
 }
diff --git a/rust/fory-derive/src/object/misc.rs b/rust/fory-derive/src/object/misc.rs
index c248b0ed4..63d730700 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -20,13 +20,8 @@ use quote::quote;
 use std::sync::atomic::{AtomicU32, Ordering};
 use syn::Field;
 
-use super::field_meta::{
-    classify_field_type, extract_option_inner_type, is_option_type, parse_field_meta,
-};
-use super::util::{
-    classify_trait_object_field, generic_tree_to_tokens, get_filtered_source_fields_iter,
-    get_sort_fields_ts, parse_generic_tree, StructField,
-};
+use super::field_codec::{build_bindings, FieldBinding};
+use super::util::get_sort_fields_ts;
 use crate::util::SourceField;
 
 // Global type ID counter that auto-grows from 0 at macro processing time
@@ -86,188 +81,13 @@ pub fn gen_get_sorted_field_names(fields: &[&Field]) -> TokenStream {
 }
 
 pub fn gen_field_fields_info(source_fields: &[SourceField<'_>]) -> TokenStream {
-    let field_infos = get_filtered_source_fields_iter(source_fields).map(|sf| {
-        let field = sf.field;
-        let ty = &field.ty;
-        let name = &sf.field_name;
-
-        // Parse field metadata for nullable/ref tracking and field ID
-        let meta = parse_field_meta(field).unwrap_or_default();
-        let type_class = classify_field_type(ty);
-        // For nullable, check both the classified type AND whether outer type is Option
-        // This handles Option<Rc<T>> correctly - classify_field_type returns Rc for track_ref,
-        // but we also need to detect that the outer wrapper is Option for nullable.
-        let is_outer_option = is_option_type(ty);
-        let nullable = meta.effective_nullable(type_class) || is_outer_option;
-        let track_ref = meta.effective_ref(type_class);
-        // Only use explicit field ID when user sets #[fory(id = N)]
-        // Otherwise use -1 to indicate field name encoding should be used
-        let field_id = if meta.uses_tag_id() {
-            meta.effective_id() as i16
-        } else {
-            -1i16 // Use field name encoding when no explicit ID
-        };
-
-        match classify_trait_object_field(ty) {
-            StructField::None => {
-                // Check if this is an i32/u32/u64 field (or Option<i32>/Option<u32>/Option<u64>) with encoding attributes
-                // In this case, we need to generate the FieldType with the correct type ID directly
-                let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
-                let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
-                    .to_string()
-                    .replace(' ', "");
-
-                let has_encoding =
-                    (inner_ty_str == "i32" || inner_ty_str == "u32" || inner_ty_str == "u64")
-                        && meta.type_id.is_some();
-                let has_array_override = matches!(
-                    meta.type_id,
-                    Some(tid)
-                        if tid == fory_core::type_id::TypeId::INT8_ARRAY as i16
-                            || tid == fory_core::type_id::TypeId::UINT8_ARRAY as i16
-                ) && (inner_ty_str == "Vec<u8>"
-                    || inner_ty_str == "Vec<i8>"
-                    || inner_ty_str.starts_with("[u8;")
-                    || inner_ty_str.starts_with("[i8;"));
-
-                if has_encoding {
-                    // Generate FieldType directly with the correct type ID based on meta.type_id
-                    let type_id_ts = match (inner_ty_str.as_str(), meta.type_id) {
-                        // i32: VARINT32 (default) or INT32 (fixed)
-                        ("i32", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
-                            quote! { fory_core::type_id::TypeId::INT32 as u32 }
-                        }
-                        ("i32", _) => {
-                            quote! { fory_core::type_id::TypeId::VARINT32 as u32 }
-                        }
-                        // u32: VAR_UINT32 (default) or UINT32 (fixed)
-                        ("u32", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
-                            quote! { fory_core::type_id::TypeId::UINT32 as u32 }
-                        }
-                        ("u32", _) => {
-                            quote! { fory_core::type_id::TypeId::VAR_UINT32 as u32 }
-                        }
-                        // u64: VAR_UINT64 (default), UINT64 (fixed), or TAGGED_UINT64 (tagged)
-                        ("u64", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
-                            quote! { fory_core::type_id::TypeId::UINT64 as u32 }
-                        }
-                        ("u64", Some(tid))
-                            if tid == fory_core::type_id::TypeId::TAGGED_UINT64 as i16 =>
-                        {
-                            quote! { fory_core::type_id::TypeId::TAGGED_UINT64 as u32 }
-                        }
-                        ("u64", _) => {
-                            quote! { fory_core::type_id::TypeId::VAR_UINT64 as u32 }
-                        }
-                        _ => unreachable!(),
-                    };
-
-                    quote! {
-                        fory_core::meta::FieldInfo::new_with_id(
-                            #field_id,
-                            #name,
-                            fory_core::meta::FieldType {
-                                type_id: #type_id_ts,
-                                user_type_id: u32::MAX,
-                                nullable: #nullable,
-                                track_ref: #track_ref,
-                                generics: Vec::new()
-                            }
-                        )
-                    }
-                } else if has_array_override {
-                    let type_id_ts = match meta.type_id {
-                        Some(tid) if tid == fory_core::type_id::TypeId::INT8_ARRAY as i16 => {
-                            quote! { fory_core::type_id::TypeId::INT8_ARRAY as u32 }
-                        }
-                        Some(tid) if tid == fory_core::type_id::TypeId::UINT8_ARRAY as i16 => {
-                            quote! { fory_core::type_id::TypeId::UINT8_ARRAY as u32 }
-                        }
-                        _ => unreachable!(),
-                    };
-                    quote! {
-                        fory_core::meta::FieldInfo::new_with_id(
-                            #field_id,
-                            #name,
-                            fory_core::meta::FieldType {
-                                type_id: #type_id_ts,
-                                user_type_id: u32::MAX,
-                                nullable: #nullable,
-                                track_ref: #track_ref,
-                                generics: Vec::new()
-                            }
-                        )
-                    }
-                } else {
-                    let generic_tree = parse_generic_tree(ty);
-                    let generic_token = generic_tree_to_tokens(&generic_tree);
-                    quote! {
-                        fory_core::meta::FieldInfo::new_with_id(
-                            #field_id,
-                            #name,
-                            {
-                                let mut ft = #generic_token;
-                                ft.nullable = #nullable;
-                                ft.track_ref = #track_ref;
-                                ft
-                            }
-                        )
-                    }
-                }
-            }
-            StructField::VecBox(_) | StructField::VecRc(_) | StructField::VecArc(_) => {
-                quote! {
-                    fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
-                        type_id: fory_core::type_id::TypeId::LIST as u32,
-                        user_type_id: u32::MAX,
-                        nullable: #nullable,
-                        track_ref: #track_ref,
-                        generics: vec![fory_core::meta::FieldType {
-                            type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
-                            user_type_id: u32::MAX,
-                            nullable: false,
-                            track_ref: false,
-                            generics: Vec::new()
-                        }]
-                    })
-                }
-            }
-            StructField::HashMapBox(key_ty, _)
-            | StructField::HashMapRc(key_ty, _)
-            | StructField::HashMapArc(key_ty, _) => {
-                let key_generic_tree = parse_generic_tree(key_ty.as_ref());
-                let key_generic_token = generic_tree_to_tokens(&key_generic_tree);
-                quote! {
-                    fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
-                        type_id: fory_core::type_id::TypeId::MAP as u32,
-                        user_type_id: u32::MAX,
-                        nullable: #nullable,
-                        track_ref: #track_ref,
-                        generics: vec![
-                            #key_generic_token,
-                            fory_core::meta::FieldType {
-                                type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
-                                user_type_id: u32::MAX,
-                                nullable: false,
-                                track_ref: false,
-                                generics: Vec::new()
-                            }
-                        ]
-                    })
-                }
-            }
-            _ => {
-                quote! {
-                    fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
-                        type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
-                        user_type_id: u32::MAX,
-                        nullable: #nullable,
-                        track_ref: #track_ref,
-                        generics: Vec::new()
-                    })
-                }
-            }
-        }
+    let bindings = match build_bindings(source_fields) {
+        Ok(bindings) => bindings,
+        Err(err) => return err.to_compile_error(),
+    };
+    let field_infos = bindings.iter().filter_map(|binding| match binding {
+        FieldBinding::Codec(binding) => Some(binding.field_info()),
+        FieldBinding::Skipped(_) => None,
     });
 
     let fields: Vec<&Field> = source_fields.iter().map(|sf| sf.field).collect();
diff --git a/rust/fory-derive/src/object/mod.rs b/rust/fory-derive/src/object/mod.rs
index d546adb45..a2b0710de 100644
--- a/rust/fory-derive/src/object/mod.rs
+++ b/rust/fory-derive/src/object/mod.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 mod derive_enum;
+mod field_codec;
 pub(crate) mod field_meta;
 mod misc;
 pub(crate) mod read;
diff --git a/rust/fory-derive/src/object/read.rs b/rust/fory-derive/src/object/read.rs
index 1e37eb6ad..00b5508e5 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -19,140 +19,10 @@ use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, quote};
 use syn::Field;
 
-use super::field_meta::{extract_option_inner_type, parse_field_meta};
-use super::util::{
-    classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc,
-    determine_field_ref_mode, extract_type_name, gen_struct_version_hash_ts,
-    get_option_inner_primitive_name, get_primitive_reader_method_with_encoding, get_struct_name,
-    is_debug_enabled, is_direct_primitive_type, is_option_encoding_primitive, is_primitive_type,
-    is_skip_field, should_skip_type_info_for_field, FieldRefMode, StructField,
-};
+use super::field_codec::{build_bindings, FieldBinding};
+use super::util::{gen_struct_version_hash_ts, get_struct_name, is_debug_enabled};
 use crate::util::SourceField;
 
-/// Check if a type is a primitive type that needs special compatible mode handling
-/// Returns the type name if it's u8, u16, u32, or u64 (or Option<u8/u16/u32/u64>)
-fn is_compatible_primitive_type(ty: &syn::Type) -> Option<&'static str> {
-    let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
-    let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
-        .to_string()
-        .replace(' ', "");
-    match inner_ty_str.as_str() {
-        "u8" => Some("u8"),
-        "u16" => Some("u16"),
-        "u32" => Some("u32"),
-        "u64" => Some("u64"),
-        _ => None,
-    }
-}
-
-/// Check if a type is u32 or u64 (for encoding-aware reading)
-fn is_unsigned_encoding_type(ty: &syn::Type) -> Option<&'static str> {
-    let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
-    let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
-        .to_string()
-        .replace(' ', "");
-    match inner_ty_str.as_str() {
-        "u32" => Some("u32"),
-        "u64" => Some("u64"),
-        _ => None,
-    }
-}
-
-/// Generate compatible mode read code for u32/u64 fields based on remote type_id
-fn gen_compatible_unsigned_read(
-    unsigned_type: &str,
-    var_name: &Ident,
-    is_option: bool,
-) -> TokenStream {
-    let read_value = if unsigned_type == "u32" {
-        quote! {
-            // Read u32 based on remote type_id
-            match _field.field_type.type_id {
-                fory_core::type_id::UINT32 => context.reader.read_u32()?,
-                fory_core::type_id::VAR_UINT32 => context.reader.read_var_u32()?,
-                _ => context.reader.read_var_u32()?, // Default to varint
-            }
-        }
-    } else {
-        // u64
-        quote! {
-            // Read u64 based on remote type_id
-            match _field.field_type.type_id {
-                fory_core::type_id::UINT64 => context.reader.read_u64()?,
-                fory_core::type_id::VAR_UINT64 => context.reader.read_var_u64()?,
-                fory_core::type_id::TAGGED_UINT64 => context.reader.read_tagged_u64()?,
-                _ => context.reader.read_var_u64()?, // Default to varint
-            }
-        }
-    };
-
-    if is_option {
-        quote! {
-            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                _field.field_type.type_id,
-                _field.field_type.nullable,
-            );
-            if read_ref_flag {
-                let ref_flag = context.reader.read_i8()?;
-                if ref_flag == fory_core::RefFlag::Null as i8 {
-                    #var_name = Some(None);
-                } else {
-                    #var_name = Some(Some(#read_value));
-                }
-            } else {
-                #var_name = Some(Some(#read_value));
-            }
-        }
-    } else {
-        quote! {
-            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                _field.field_type.type_id,
-                _field.field_type.nullable,
-            );
-            if read_ref_flag {
-                let ref_flag = context.reader.read_i8()?;
-                if ref_flag == fory_core::RefFlag::Null as i8 {
-                    // Remote sent null but local field is non-nullable, use default
-                    #var_name = 0;
-                } else {
-                    #var_name = #read_value;
-                }
-            } else {
-                #var_name = #read_value;
-            }
-        }
-    }
-}
-
-/// Generate compatible mode read code for u8/u16 Option fields
-/// These need special handling because when remote field is non-nullable,
-/// Java sends just the raw bytes without a ref flag
-fn gen_compatible_primitive_option_read(prim_type: &str, var_name: &Ident) -> TokenStream {
-    let read_value = match prim_type {
-        "u8" => quote! { context.reader.read_u8()? },
-        "u16" => quote! { context.reader.read_u16()? },
-        _ => unreachable!("Only u8/u16 should use this function"),
-    };
-
-    quote! {
-        let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-            _field.field_type.type_id,
-            _field.field_type.nullable,
-        );
-        if read_ref_flag {
-            let ref_flag = context.reader.read_i8()?;
-            if ref_flag == fory_core::RefFlag::Null as i8 {
-                #var_name = Some(None);
-            } else {
-                #var_name = Some(Some(#read_value));
-            }
-        } else {
-            // Remote field is non-nullable, read raw value directly
-            #var_name = Some(Some(#read_value));
-        }
-    }
-}
-
 /// Create a private variable name for a field during deserialization.
 /// For named fields: `_field_name`
 /// For tuple struct fields: `_0`, `_1`, etc.
@@ -163,56 +33,16 @@ pub(crate) fn create_private_field_name(field: &Field, index: usize) -> Ident {
     }
 }
 
-fn need_declared_by_option(field: &Field) -> bool {
-    let type_name = extract_type_name(&field.ty);
-    type_name == "Option" || !is_primitive_type(type_name.as_str())
-}
-
-fn is_float16_like(type_name: &str) -> bool {
-    type_name == "float16" || type_name == "Float16"
-}
-
-fn is_bfloat16_like(type_name: &str) -> bool {
-    type_name == "bfloat16" || type_name == "BFloat16"
-}
-
 pub(crate) fn declare_var(source_fields: &[SourceField<'_>]) -> Vec<TokenStream> {
-    source_fields
+    let bindings = match build_bindings(source_fields) {
+        Ok(bindings) => bindings,
+        Err(err) => return vec![err.to_compile_error()],
+    };
+    bindings
         .iter()
-        .map(|sf| {
-            let field = sf.field;
-            let ty = &field.ty;
-            let var_name = create_private_field_name(field, sf.original_index);
-            match classify_trait_object_field(ty) {
-                StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => {
-                    quote! {
-                        let mut #var_name: #ty = <#ty as fory_core::serializer::ForyDefault>::fory_default();
-                    }
-                }
-                _ => {
-                    if need_declared_by_option(field) {
-                        quote! {
-                            let mut #var_name: Option<#ty> = None;
-                        }
-                    } else if is_float16_like(extract_type_name(&field.ty).as_str()) {
-                        quote! {
-                            let mut #var_name: fory_core::types::float16::float16 = fory_core::types::float16::float16::ZERO;
-                        }
-                    } else if is_bfloat16_like(extract_type_name(&field.ty).as_str()) {
-                        quote! {
-                            let mut #var_name: fory_core::types::bfloat16::bfloat16 = fory_core::types::bfloat16::bfloat16::ZERO;
-                        }
-                    } else if extract_type_name(&field.ty) == "bool" {
-                        quote! {
-                            let mut #var_name: bool = false;
-                        }
-                    } else {
-                        quote! {
-                            let mut #var_name: #ty = 0 as #ty;
-                        }
-                    }
-                }
-            }
+        .map(|binding| match binding {
+            FieldBinding::Codec(binding) => binding.declare_compatible_var(),
+            FieldBinding::Skipped(binding) => binding.read_default(),
         })
         .collect()
 }
@@ -222,27 +52,19 @@ pub(crate) fn assign_value(source_fields: &[SourceField<'_>]) -> Vec<TokenStream
         .first()
         .map(|sf| sf.is_tuple_struct)
         .unwrap_or(false);
+    let bindings = match build_bindings(source_fields) {
+        Ok(bindings) => bindings,
+        Err(err) => return vec![err.to_compile_error()],
+    };
 
     // Generate field value expressions with original index for sorting
     let mut indexed: Vec<_> = source_fields
         .iter()
-        .map(|sf| {
-            let var_name = create_private_field_name(sf.field, sf.original_index);
-            let value_expr = match classify_trait_object_field(&sf.field.ty) {
-                StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => {
-                    quote! { #var_name }
-                }
-                StructField::ContainsTraitObject => {
-                    quote! { #var_name.unwrap() }
-                }
-                _ => {
-                    if need_declared_by_option(sf.field) {
-                        let ty = &sf.field.ty;
-                        quote! { #var_name.unwrap_or_else(|| <#ty as fory_core::ForyDefault>::fory_default()) }
-                    } else {
-                        quote! { #var_name }
-                    }
-                }
+        .zip(bindings.iter())
+        .map(|(sf, binding)| {
+            let value_expr = match binding {
+                FieldBinding::Codec(binding) => binding.assign_value(),
+                FieldBinding::Skipped(binding) => binding.assign_value(),
             };
             (sf.original_index, sf.field_init(value_expr))
         })
@@ -256,205 +78,6 @@ pub(crate) fn assign_value(source_fields: &[SourceField<'_>]) -> Vec<TokenStream
     indexed.into_iter().map(|(_, ts)| ts).collect()
 }
 
-pub fn gen_read_field(field: &Field, private_ident: &Ident, field_name: &str) -> TokenStream {
-    let ty = &field.ty;
-    if is_skip_field(field) {
-        return quote! {
-            let #private_ident = <#ty as fory_core::ForyDefault>::fory_default();
-        };
-    }
-
-    // Determine RefMode from field meta (respects #[fory(ref=false)] attribute)
-    let ref_mode = determine_field_ref_mode(field);
-    let base = match classify_trait_object_field(ty) {
-        StructField::BoxDyn => {
-            // Box<dyn Trait> - respect field meta for ref mode
-            quote! {
-                let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-            }
-        }
-        StructField::RcDyn(trait_name) => {
-            // Rc<dyn Trait> - respect field meta for ref mode
-            let types = create_wrapper_types_rc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-                let #private_ident = std::rc::Rc::<dyn #trait_ident>::from(wrapper);
-            }
-        }
-        StructField::ArcDyn(trait_name) => {
-            // Arc<dyn Trait> - respect field meta for ref mode
-            let types = create_wrapper_types_arc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-                let #private_ident = std::sync::Arc::<dyn #trait_ident>::from(wrapper);
-            }
-        }
-        StructField::VecRc(trait_name) => {
-            // Vec<Rc<dyn Trait>> - respect field meta for ref mode
-            let types = create_wrapper_types_rc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                let #private_ident = wrapper_vec.into_iter()
-                    .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
-                    .collect();
-            }
-        }
-        StructField::VecArc(trait_name) => {
-            // Vec<Arc<dyn Trait>> - respect field meta for ref mode
-            let types = create_wrapper_types_arc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                let #private_ident = wrapper_vec.into_iter()
-                    .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
-                    .collect();
-            }
-        }
-        StructField::VecBox(_) => {
-            // Vec<Box<dyn Any>> - respect field meta for ref mode
-            quote! {
-                let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-            }
-        }
-        StructField::HashMapBox(_, _) => {
-            // HashMap<K, Box<dyn Any>> - respect field meta for ref mode
-            quote! {
-                let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-            }
-        }
-        StructField::HashMapRc(key_ty, trait_name) => {
-            // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode
-            let types = create_wrapper_types_rc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                let #private_ident = wrapper_map.into_iter()
-                    .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v)))
-                    .collect();
-            }
-        }
-        StructField::HashMapArc(key_ty, trait_name) => {
-            // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode
-            let types = create_wrapper_types_arc(&trait_name);
-            let wrapper_ty = types.wrapper_ty;
-            let trait_ident = types.trait_ident;
-            quote! {
-                let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                let #private_ident = wrapper_map.into_iter()
-                    .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v)))
-                    .collect();
-            }
-        }
-        StructField::Forward => {
-            // Forward types (trait objects, forward references) - polymorphic, always need type info
-            quote! {
-                let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-            }
-        }
-        _ => {
-            let skip_type_info = should_skip_type_info_for_field(ty);
-            let meta = parse_field_meta(field).unwrap_or_default();
-
-            // Check if this is Option<u32> or Option<u64> with encoding attributes
-            // These need special inline handling because the generic Option<T> serializer
-            // doesn't know about field-level encoding attributes.
-            if is_option_encoding_primitive(ty, &meta) {
-                let inner_name = get_option_inner_primitive_name(ty).unwrap();
-                let reader_method = get_primitive_reader_method_with_encoding(inner_name, &meta);
-                let reader_ident = syn::Ident::new(reader_method, proc_macro2::Span::call_site());
-                // For Option<primitive>, read null flag first, then value if not null
-                quote! {
-                    let ref_flag = context.reader.read_i8()?;
-                    let #private_ident = if ref_flag == fory_core::RefFlag::Null as i8 {
-                        None
-                    } else {
-                        Some(context.reader.#reader_ident()?)
-                    };
-                }
-            }
-            // Check if this is a direct primitive type that can use direct reader calls
-            // Only apply when ref_mode is None (no ref tracking needed)
-            else if ref_mode == FieldRefMode::None && is_direct_primitive_type(ty) {
-                let type_name = extract_type_name(ty);
-                if type_name == "String" {
-                    // String: call fory_read_data directly
-                    quote! {
-                        let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?;
-                    }
-                } else {
-                    // Numeric primitives: use direct buffer methods
-                    // For u32/u64, consider encoding attributes
-                    let reader_method =
-                        get_primitive_reader_method_with_encoding(&type_name, &meta);
-                    let reader_ident =
-                        syn::Ident::new(reader_method, proc_macro2::Span::call_site());
-                    quote! {
-                        let #private_ident = context.reader.#reader_ident()?;
-                    }
-                }
-            } else if skip_type_info {
-                // Known types (primitives, strings, collections) - skip type info at compile time
-                if ref_mode == FieldRefMode::None {
-                    quote! {
-                        let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?;
-                    }
-                } else {
-                    quote! {
-                        let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                    }
-                }
-            } else {
-                // Custom types (struct/enum/ext) - always need mode-dependent type info logic
-                // Determine read_type_info based on mode:
-                // - compatible=true: use need_to_write_type_for_field (struct types need type info)
-                // - compatible=false: use fory_is_polymorphic
-                // This applies regardless of ref_mode because Java always writes type info
-                // for struct-type fields in compatible mode, even for non-nullable fields.
-                quote! {
-                    let read_type_info = if context.is_compatible() {
-                        fory_core::type_id::need_to_write_type_for_field(
-                            <#ty as fory_core::Serializer>::fory_static_type_id()
-                        )
-                    } else {
-                        <#ty as fory_core::Serializer>::fory_is_polymorphic()
-                    };
-                    let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, read_type_info)?;
-                }
-            }
-        }
-    };
-
-    if is_debug_enabled() {
-        let struct_name = get_struct_name().expect("struct context not set");
-        let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
-        let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site());
-        quote! {
-            fory_core::serializer::struct_::struct_before_read_field(
-                #struct_name_lit,
-                #field_name_lit,
-                context,
-            );
-            #base
-            fory_core::serializer::struct_::struct_after_read_field(
-                #struct_name_lit,
-                #field_name_lit,
-                (&#private_ident) as &dyn std::any::Any,
-                context,
-            );
-        }
-    } else {
-        base
-    }
-}
-
 pub fn gen_read_type_info() -> TokenStream {
     quote! {
         fory_core::serializer::struct_::read_type_info_fast::<Self>(context)
@@ -462,11 +85,43 @@ pub fn gen_read_type_info() -> TokenStream {
 }
 
 fn get_source_fields_loop_ts(source_fields: &[SourceField<'_>]) -> TokenStream {
-    let read_fields_ts: Vec<_> = source_fields
+    let bindings = match build_bindings(source_fields) {
+        Ok(bindings) => bindings,
+        Err(err) => return err.to_compile_error(),
+    };
+    let read_fields_ts: Vec<_> = bindings
         .iter()
-        .map(|sf| {
-            let private_ident = create_private_field_name(sf.field, sf.original_index);
-            gen_read_field(sf.field, &private_ident, &sf.field_name)
+        .map(|binding| match binding {
+            FieldBinding::Codec(binding) => {
+                let base = binding.read_field();
+                if is_debug_enabled() {
+                    let struct_name = get_struct_name().expect("struct context not set");
+                    let struct_name_lit =
+                        syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
+                    let field_name_lit = syn::LitStr::new(
+                        &binding.source.field_name,
+                        proc_macro2::Span::call_site(),
+                    );
+                    let private_ident = &binding.private_ident;
+                    quote! {
+                        fory_core::serializer::struct_::struct_before_read_field(
+                            #struct_name_lit,
+                            #field_name_lit,
+                            context,
+                        );
+                        #base
+                        fory_core::serializer::struct_::struct_after_read_field(
+                            #struct_name_lit,
+                            #field_name_lit,
+                            (&#private_ident) as &dyn std::any::Any,
+                            context,
+                        );
+                    }
+                } else {
+                    base
+                }
+            }
+            FieldBinding::Skipped(binding) => binding.read_default(),
         })
         .collect();
     quote! {
@@ -522,359 +177,6 @@ pub fn gen_read_data(source_fields: &[SourceField<'_>]) -> TokenStream {
     }
 }
 
-pub(crate) fn gen_read_compatible_match_arm_body(
-    field: &Field,
-    var_name: &Ident,
-    field_name: &str,
-) -> TokenStream {
-    let ty = &field.ty;
-    let field_kind = classify_trait_object_field(ty);
-    let is_skip_flag = is_skip_field(field);
-    // Determine RefMode from field meta (respects #[fory(ref=false)] attribute)
-    let ref_mode = determine_field_ref_mode(field);
-
-    let base = if is_skip_flag {
-        match field_kind {
-            StructField::None => {
-                let dec_by_option = need_declared_by_option(field);
-                if dec_by_option {
-                    quote! {
-                        #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default());
-                    }
-                } else {
-                    quote! {
-                        #var_name = <#ty as fory_core::ForyDefault>::fory_default();
-                    }
-                }
-            }
-            _ => {
-                quote! {
-                    #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default());
-                }
-            }
-        }
-    } else {
-        match field_kind {
-            StructField::BoxDyn => {
-                // Box<dyn Trait> - respect field meta for ref mode
-                quote! {
-                    #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?);
-                }
-            }
-            StructField::RcDyn(trait_name) => {
-                // Rc<dyn Trait> - respect field meta for ref mode
-                let types = create_wrapper_types_rc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-                    #var_name = Some(std::rc::Rc::<dyn #trait_ident>::from(wrapper));
-                }
-            }
-            StructField::ArcDyn(trait_name) => {
-                // Arc<dyn Trait> - respect field meta for ref mode
-                let types = create_wrapper_types_arc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
-                    #var_name = Some(std::sync::Arc::<dyn #trait_ident>::from(wrapper));
-                }
-            }
-            StructField::VecRc(trait_name) => {
-                // Vec<Rc<dyn Trait>> - respect field meta for ref mode
-                let types = create_wrapper_types_rc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                    #var_name = Some(wrapper_vec.into_iter()
-                        .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
-                        .collect());
-                }
-            }
-            StructField::VecArc(trait_name) => {
-                // Vec<Arc<dyn Trait>> - respect field meta for ref mode
-                let types = create_wrapper_types_arc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                    #var_name = Some(wrapper_vec.into_iter()
-                        .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
-                        .collect());
-                }
-            }
-            StructField::VecBox(_) => {
-                // Vec<Box<dyn Any>> uses standard Vec deserialization with polymorphic elements
-                // Check nullable and track_ref flags from remote field info
-                quote! {
-                    let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                        _field.field_type.type_id,
-                        _field.field_type.nullable,
-                    );
-                    let ref_mode = if _field.field_type.track_ref {
-                        fory_core::RefMode::Tracking
-                    } else if read_ref_flag {
-                        fory_core::RefMode::NullOnly
-                    } else {
-                        fory_core::RefMode::None
-                    };
-                    if read_ref_flag || _field.field_type.track_ref {
-                        #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
-                    } else {
-                        #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
-                    }
-                }
-            }
-            StructField::HashMapBox(_, _) => {
-                // HashMap<K, Box<dyn Any>> uses standard HashMap deserialization with polymorphic values
-                // Check nullable and track_ref flags from remote field info
-                quote! {
-                    let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                        _field.field_type.type_id,
-                        _field.field_type.nullable,
-                    );
-                    let ref_mode = if _field.field_type.track_ref {
-                        fory_core::RefMode::Tracking
-                    } else if read_ref_flag {
-                        fory_core::RefMode::NullOnly
-                    } else {
-                        fory_core::RefMode::None
-                    };
-                    if read_ref_flag || _field.field_type.track_ref {
-                        #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
-                    } else {
-                        #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
-                    }
-                }
-            }
-            StructField::HashMapRc(key_ty, trait_name) => {
-                // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode
-                let types = create_wrapper_types_rc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                    #var_name = Some(wrapper_map.into_iter()
-                        .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v)))
-                        .collect());
-                }
-            }
-            StructField::HashMapArc(key_ty, trait_name) => {
-                // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode
-                let types = create_wrapper_types_arc(&trait_name);
-                let wrapper_ty = types.wrapper_ty;
-                let trait_ident = types.trait_ident;
-                quote! {
-                    let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
-                    #var_name = Some(wrapper_map.into_iter()
-                        .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v)))
-                        .collect());
-                }
-            }
-            StructField::ContainsTraitObject => {
-                // Struct containing trait objects - respect field meta for ref mode
-                quote! {
-                    #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?);
-                }
-            }
-            StructField::Forward => {
-                // Forward types (trait objects, forward references) - polymorphic, always need type info
-                // Use remote field's track_ref flag for ref_mode
-                quote! {
-                    let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                        _field.field_type.type_id,
-                        _field.field_type.nullable,
-                    );
-                    // Use RefMode::Tracking if remote field has track_ref enabled
-                    let ref_mode = if _field.field_type.track_ref {
-                        fory_core::RefMode::Tracking
-                    } else if read_ref_flag {
-                        fory_core::RefMode::NullOnly
-                    } else {
-                        fory_core::RefMode::None
-                    };
-                    // Forward types are polymorphic, always read type info
-                    #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, true)?);
-                }
-            }
-            StructField::None => {
-                let skip_type_info = should_skip_type_info_for_field(ty);
-                let dec_by_option = need_declared_by_option(field);
-                let is_option_type = extract_option_inner_type(ty).is_some();
-
-                // Check if this is a u32/u64 field that needs encoding-aware reading
-                if let Some(unsigned_type) = is_unsigned_encoding_type(ty) {
-                    gen_compatible_unsigned_read(
-                        unsigned_type,
-                        var_name,
-                        is_option_type || dec_by_option,
-                    )
-                } else if is_option_type {
-                    // Check if it's Option<u8> or Option<u16> which need special handling
-                    if let Some(prim_type) = is_compatible_primitive_type(ty) {
-                        if prim_type == "u8" || prim_type == "u16" {
-                            gen_compatible_primitive_option_read(prim_type, var_name)
-                        } else {
-                            // u32/u64 handled above
-                            unreachable!()
-                        }
-                    } else if skip_type_info {
-                        // Non-primitive Option type with skip_type_info
-                        quote! {
-                            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                                _field.field_type.type_id,
-                                _field.field_type.nullable,
-                            );
-                            // Use RefMode::Tracking if remote field has track_ref enabled
-                            let ref_mode = if _field.field_type.track_ref {
-                                fory_core::RefMode::Tracking
-                            } else if read_ref_flag {
-                                fory_core::RefMode::NullOnly
-                            } else {
-                                fory_core::RefMode::None
-                            };
-                            if read_ref_flag || _field.field_type.track_ref {
-                                #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
-                            } else {
-                                #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
-                            }
-                        }
-                    } else {
-                        // Non-primitive Option type without skip_type_info
-                        quote! {
-                            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                                _field.field_type.type_id,
-                                _field.field_type.nullable,
-                            );
-                            // Use RefMode::Tracking if remote field has track_ref enabled
-                            let ref_mode = if _field.field_type.track_ref {
-                                fory_core::RefMode::Tracking
-                            } else if read_ref_flag {
-                                fory_core::RefMode::NullOnly
-                            } else {
-                                fory_core::RefMode::None
-                            };
-                            // For ref-tracked struct types, Java writes type info after RefValue flag
-                            let read_type_info = fory_core::type_id::need_to_write_type_for_field(
-                                <#ty as fory_core::Serializer>::fory_static_type_id()
-                            );
-                            #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?);
-                        }
-                    }
-                } else if skip_type_info {
-                    if dec_by_option {
-                        quote! {
-                            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                                _field.field_type.type_id,
-                                _field.field_type.nullable,
-                            );
-                            // Use RefMode::Tracking if remote field has track_ref enabled
-                            let ref_mode = if _field.field_type.track_ref {
-                                fory_core::RefMode::Tracking
-                            } else if read_ref_flag {
-                                fory_core::RefMode::NullOnly
-                            } else {
-                                fory_core::RefMode::None
-                            };
-                            if read_ref_flag || _field.field_type.track_ref {
-                                #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
-                            } else {
-                                #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
-                            }
-                        }
-                    } else {
-                        quote! {
-                            let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                                _field.field_type.type_id,
-                                _field.field_type.nullable,
-                            );
-                            // Use RefMode::Tracking if remote field has track_ref enabled
-                            let ref_mode = if _field.field_type.track_ref {
-                                fory_core::RefMode::Tracking
-                            } else if read_ref_flag {
-                                fory_core::RefMode::NullOnly
-                            } else {
-                                fory_core::RefMode::None
-                            };
-                            if read_ref_flag || _field.field_type.track_ref {
-                                #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?;
-                            } else {
-                                #var_name = <#ty as fory_core::Serializer>::fory_read_data(context)?;
-                            }
-                        }
-                    }
-                } else if dec_by_option {
-                    quote! {
-                        let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                            _field.field_type.type_id,
-                            _field.field_type.nullable,
-                        );
-                        // Use RefMode::Tracking if remote field has track_ref enabled
-                        let ref_mode = if _field.field_type.track_ref {
-                            fory_core::RefMode::Tracking
-                        } else if read_ref_flag {
-                            fory_core::RefMode::NullOnly
-                        } else {
-                            fory_core::RefMode::None
-                        };
-                        // For ref-tracked struct types, Java writes type info after RefValue flag
-                        let read_type_info = fory_core::type_id::need_to_write_type_for_field(
-                            <#ty as fory_core::Serializer>::fory_static_type_id()
-                        );
-                        #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?);
-                    }
-                } else {
-                    quote! {
-                        let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
-                            _field.field_type.type_id,
-                            _field.field_type.nullable,
-                        );
-                        // Use RefMode::Tracking if remote field has track_ref enabled
-                        let ref_mode = if _field.field_type.track_ref {
-                            fory_core::RefMode::Tracking
-                        } else if read_ref_flag {
-                            fory_core::RefMode::NullOnly
-                        } else {
-                            fory_core::RefMode::None
-                        };
-                        // For ref-tracked struct types, Java writes type info after RefValue flag
-                        let read_type_info = fory_core::type_id::need_to_write_type_for_field(
-                            <#ty as fory_core::Serializer>::fory_static_type_id()
-                        );
-                        #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?;
-                    }
-                }
-            }
-        }
-    };
-
-    if is_debug_enabled() {
-        let struct_name = get_struct_name().expect("struct context not set");
-        let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
-        let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site());
-        quote! {
-            fory_core::serializer::struct_::struct_before_read_field(
-                #struct_name_lit,
-                #field_name_lit,
-                context,
-            );
-            #base
-            fory_core::serializer::struct_::struct_after_read_field(
-                #struct_name_lit,
-                #field_name_lit,
-                (&#var_name) as &dyn std::any::Any,
-                context,
-            );
-        }
-    } else {
-        quote! {
-            #base
-        }
-    }
-}
-
 pub fn gen_read(_struct_ident: &Ident) -> TokenStream {
     // Note: We use `Self` instead of `#struct_ident` to correctly handle generic types.
     // When the struct has generics (e.g., LeaderId<C>), using `Self` ensures the full
@@ -950,21 +252,33 @@ pub(crate) fn gen_read_compatible_with_construction(
     source_fields: &[SourceField<'_>],
     variant_ident: Option<&Ident>,
 ) -> TokenStream {
+    let bindings = match build_bindings(source_fields) {
+        Ok(bindings) => bindings,
+        Err(err) => return err.to_compile_error(),
+    };
     let declare_ts: Vec<TokenStream> = declare_var(source_fields);
     let assign_ts: Vec<TokenStream> = assign_value(source_fields);
 
-    let match_arms: Vec<TokenStream> = source_fields
+    let match_arms: Vec<TokenStream> = bindings
         .iter()
+        .filter_map(|binding| match binding {
+            FieldBinding::Codec(binding) => Some(binding),
+            FieldBinding::Skipped(_) => None,
+        })
         .enumerate()
-        .map(|(sorted_idx, sf)| {
-            let var_name = create_private_field_name(sf.field, sf.original_index);
+        .map(|(sorted_idx, binding)| {
             // Use sorted index for matching. The assign_field_ids function assigns
             // the sorted index to matched fields after lookup (by ID or name).
-            // Fields that don't match or have type mismatches get field_id = -1.
+            // Fields that don't match get field_id = -1; codec incompatibility is
+            // handled inside the arm by reading or skipping the remote payload.
             let field_id = sorted_idx as i16;
-            let body = gen_read_compatible_match_arm_body(sf.field, &var_name, &sf.field_name);
+            let field_index = sorted_idx;
+            let body = binding.read_compatible();
             quote! {
                 #field_id => {
+                    let local_field_type = unsafe {
+                        &(*local_fields_ptr.add(#field_index)).field_type
+                    };
                     #body
                 }
             }
@@ -1029,53 +343,54 @@ pub(crate) fn gen_read_compatible_with_construction(
 
     let variant_field_remap = if let Some(variant) = variant_ident {
         let variant_name = variant.to_string();
+        let enum_name = get_struct_name().expect("enum context not set");
+        let meta_type_ident = Ident::new(
+            &format!("{}_{}VariantMeta", enum_name, variant),
+            proc_macro2::Span::call_site(),
+        );
+        let variant_name_lit = syn::LitStr::new(&variant_name, proc_macro2::Span::call_site());
         quote! {
-            if let Ok(variants_info) =
-                <Self as fory_core::StructSerializer>::fory_variants_fields_info(
-                    context.get_type_resolver(),
-                )
-            {
-                if let Some((_, _, local_fields)) =
-                    variants_info.iter().find(|(name, _, _)| name == #variant_name)
-                {
-                    let field_index_by_name: std::collections::HashMap<_, _> = local_fields
-                        .iter()
-                        .enumerate()
-                        .filter(|(_, f)| !f.field_name.is_empty())
-                        .map(|(i, f)| (f.field_name.clone(), (i, f)))
-                        .collect();
-
-                    let field_index_by_id: std::collections::HashMap<_, _> = local_fields
-                        .iter()
-                        .enumerate()
-                        .filter(|(_, f)| f.field_id >= 0)
-                        .map(|(i, f)| (f.field_id, (i, f)))
-                        .collect();
-
-                    for field in fields.iter_mut() {
-                        let local_match = if field.field_id >= 0 && field.field_name.is_empty() {
-                            field_index_by_id.get(&field.field_id).copied()
-                        } else {
-                            let snake_case_name =
-                                fory_core::util::to_snake_case(&field.field_name);
-                            field_index_by_name.get(&snake_case_name).copied()
-                        };
+            let local_variant_type_info = context
+                .get_type_resolver()
+                .get_type_info(&std::any::TypeId::of::<#meta_type_ident>())
+                .map_err(|_| fory_core::Error::type_error(
+                    concat!("Local enum variant metadata not found for ", #variant_name_lit)
+                ))?;
+            let local_variant_type_meta = local_variant_type_info.get_type_meta();
+            let local_fields = local_variant_type_meta.get_field_infos();
+
+            let field_index_by_name: std::collections::HashMap<_, _> = local_fields
+                .iter()
+                .enumerate()
+                .filter(|(_, f)| !f.field_name.is_empty())
+                .map(|(i, f)| (f.field_name.clone(), (i, f)))
+                .collect();
+
+            let field_index_by_id: std::collections::HashMap<_, _> = local_fields
+                .iter()
+                .enumerate()
+                .filter(|(_, f)| f.field_id >= 0)
+                .map(|(i, f)| (f.field_id, (i, f)))
+                .collect();
+
+            for field in fields.iter_mut() {
+                let local_match = if field.field_id >= 0 && field.field_name.is_empty() {
+                    field_index_by_id.get(&field.field_id).copied()
+                } else {
+                    let snake_case_name =
+                        fory_core::util::to_snake_case(&field.field_name);
+                    field_index_by_name.get(&snake_case_name).copied()
+                };
 
-                        match local_match {
-                            Some((sorted_index, local_info)) => {
-                                if field.field_name.is_empty() {
-                                    field.field_name = local_info.field_name.clone();
-                                }
-                                if field.field_type != local_info.field_type {
-                                    field.field_id = -1;
-                                } else {
-                                    field.field_id = sorted_index as i16;
-                                }
-                            }
-                            None => {
-                                field.field_id = -1;
-                            }
+                match local_match {
+                    Some((sorted_index, local_info)) => {
+                        if field.field_name.is_empty() {
+                            field.field_name = local_info.field_name.clone();
                         }
+                        field.field_id = sorted_index as i16;
+                    }
+                    None => {
+                        field.field_id = -1;
                     }
                 }
             }
@@ -1088,9 +403,11 @@ pub(crate) fn gen_read_compatible_with_construction(
         quote! {
             let mut fields = remote_meta.get_field_infos().clone();
             #variant_field_remap
+            let local_fields_ptr = local_fields.as_ptr();
         }
     } else {
         quote! {
+            let local_fields_ptr = meta.get_field_infos().as_ptr();
             let fields = remote_meta.get_field_infos();
         }
     };
diff --git a/rust/fory-derive/src/object/serializer.rs b/rust/fory-derive/src/object/serializer.rs
index 1ee2b0d22..605c3c452 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -41,19 +41,8 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
     let name = &ast.ident;
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
 
-    // Extract type parameter names from generics (e.g., "C", "T", "E")
-    let type_params: std::collections::HashSet<String> = ast
-        .generics
-        .params
-        .iter()
-        .filter_map(|p| match p {
-            syn::GenericParam::Type(tp) => Some(tp.ident.to_string()),
-            _ => None, // Ignore lifetime and const parameters
-        })
-        .collect();
-
     use crate::object::util::{clear_struct_context, set_struct_context};
-    set_struct_context(&name.to_string(), attrs.debug_enabled, type_params);
+    set_struct_context(&name.to_string(), attrs.debug_enabled);
 
     // Check if ForyDefault is already derived/implemented
     let has_existing_default = has_existing_default(ast, "ForyDefault");
@@ -123,7 +112,6 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
     ) = match &ast.data {
         syn::Data::Struct(s) => {
             let source_fields = source_fields(&s.fields);
-            let fields = extract_fields(&source_fields);
             (
                 write::gen_write(),
                 write::gen_write_data(&source_fields),
@@ -132,7 +120,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
                 read::gen_read_with_type_info(),
                 read::gen_read_data(&source_fields),
                 read::gen_read_type_info(),
-                write::gen_reserved_space(&fields),
+                write::gen_reserved_space(&source_fields),
                 quote! { fory_core::TypeId::STRUCT },
             )
         }
@@ -284,43 +272,11 @@ fn generate_default_impl(
                 .map(|sf| sf.is_tuple_struct)
                 .unwrap_or(false);
 
-            use super::util::{
-                classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc,
-                StructField,
-            };
-
             // Generate field initializations with original index for sorting
             let mut indexed: Vec<_> = source_fields
                 .iter()
                 .map(|sf| {
-                    let ty = &sf.field.ty;
-                    let value = match classify_trait_object_field(ty) {
-                        StructField::RcDyn(trait_name) => {
-                            let types = create_wrapper_types_rc(&trait_name);
-                            let wrapper_ty = types.wrapper_ty;
-                            let trait_ident = types.trait_ident;
-                            quote! {
-                                {
-                                    let wrapper = #wrapper_ty::default();
-                                    std::rc::Rc::<dyn #trait_ident>::from(wrapper)
-                                }
-                            }
-                        }
-                        StructField::ArcDyn(trait_name) => {
-                            let types = create_wrapper_types_arc(&trait_name);
-                            let wrapper_ty = types.wrapper_ty;
-                            let trait_ident = types.trait_ident;
-                            quote! {
-                                {
-                                    let wrapper = #wrapper_ty::default();
-                                    std::sync::Arc::<dyn #trait_ident>::from(wrapper)
-                                }
-                            }
-                        }
-                        _ => {
-                            quote! { <#ty as fory_core::ForyDefault>::fory_default() }
-                        }
-                    };
+                    let value = super::field_codec::default_expr_for_type(&sf.field.ty);
                     (sf.original_index, sf.field_init(value))
                 })
                 .collect();
diff --git a/rust/fory-derive/src/object/util.rs b/rust/fory-derive/src/object/util.rs
index 53047951f..7cdabb2e4 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -15,16 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::util::{
-    detect_collection_with_trait_object, is_arc_dyn_trait, is_box_dyn_trait, is_rc_dyn_trait,
-    CollectionTraitInfo,
-};
-use fory_core::type_id::{TypeId, PRIMITIVE_ARRAY_TYPE_MAP};
+use super::field_meta::IntEncoding;
+use fory_core::type_id::TypeId;
 use fory_core::util::to_snake_case;
-use proc_macro2::{Ident, TokenStream};
-use quote::{format_ident, quote, ToTokens};
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
 use std::cell::RefCell;
-use std::fmt;
 use syn::{Field, GenericArgument, Index, PathArguments, Type};
 
 /// Get field name for a field, handling both named and tuple struct fields.
@@ -69,24 +65,14 @@ thread_local! {
 struct MacroContext {
     struct_name: String,
     debug_enabled: bool,
-    /// Type parameter names extracted from the struct/enum generics (e.g., "C", "T", "E")
-    type_params: std::collections::HashSet<String>,
 }
 
-/// Set the macro context with struct name, debug flag, and type parameters.
-///
-/// `type_params` should contain the names of all type parameters from the struct/enum
-/// generics (e.g., for `struct Vote<C: RaftTypeConfig>`, pass `{"C"}`).
-pub(super) fn set_struct_context(
-    name: &str,
-    debug_enabled: bool,
-    type_params: std::collections::HashSet<String>,
-) {
+/// Set the macro context with struct name and debug flag.
+pub(super) fn set_struct_context(name: &str, debug_enabled: bool) {
     MACRO_CONTEXT.with(|ctx| {
         *ctx.borrow_mut() = Some(MacroContext {
             struct_name: name.to_string(),
             debug_enabled,
-            type_params,
         });
     });
 }
@@ -110,79 +96,6 @@ pub(super) fn is_debug_enabled() -> bool {
     })
 }
 
-/// Check if a type name is a type parameter of the current struct/enum.
-pub(super) fn is_type_parameter(name: &str) -> bool {
-    MACRO_CONTEXT.with(|ctx| {
-        ctx.borrow()
-            .as_ref()
-            .map(|c| c.type_params.contains(name))
-            .unwrap_or(false)
-    })
-}
-
-pub(super) fn contains_trait_object(ty: &Type) -> bool {
-    match ty {
-        Type::TraitObject(_) => true,
-        Type::Path(type_path) => {
-            if is_box_dyn_trait(ty).is_some()
-                || is_rc_dyn_trait(ty).is_some()
-                || is_arc_dyn_trait(ty).is_some()
-            {
-                return true;
-            }
-
-            if let Some(seg) = type_path.path.segments.last() {
-                if let PathArguments::AngleBracketed(args) = &seg.arguments {
-                    return args.args.iter().any(|arg| {
-                        if let GenericArgument::Type(inner_ty) = arg {
-                            contains_trait_object(inner_ty)
-                        } else {
-                            false
-                        }
-                    });
-                }
-            }
-            false
-        }
-        _ => false,
-    }
-}
-
-pub(super) struct WrapperTypes {
-    pub wrapper_ty: Ident,
-    pub trait_ident: Ident,
-}
-
-pub(super) fn create_wrapper_types_rc(trait_name: &str) -> WrapperTypes {
-    WrapperTypes {
-        wrapper_ty: format_ident!("{}Rc", trait_name),
-        trait_ident: format_ident!("{}", trait_name),
-    }
-}
-
-pub(super) fn create_wrapper_types_arc(trait_name: &str) -> WrapperTypes {
-    WrapperTypes {
-        wrapper_ty: format_ident!("{}Arc", trait_name),
-        trait_ident: format_ident!("{}", trait_name),
-    }
-}
-
-#[allow(dead_code)]
-pub(super) enum StructField {
-    BoxDyn,
-    RcDyn(String),
-    ArcDyn(String),
-    VecBox(String),
-    VecRc(String),
-    VecArc(String),
-    HashMapBox(Box<Type>, String),
-    HashMapRc(Box<Type>, String),
-    HashMapArc(Box<Type>, String),
-    ContainsTraitObject,
-    Forward,
-    None,
-}
-
 fn is_forward_field(ty: &Type) -> bool {
     let struct_name = match get_struct_name() {
         Some(name) => name,
@@ -258,435 +171,6 @@ fn is_forward_field_internal(ty: &Type, struct_name: &str) -> bool {
     }
 }
 
-pub(super) fn classify_trait_object_field(ty: &Type) -> StructField {
-    // Check collections FIRST - they should be handled as collections, not forward refs
-    // This is important because is_forward_field would match Vec<Box<dyn Any>> as forward
-    if let Some(collection_info) = detect_collection_with_trait_object(ty) {
-        return match collection_info {
-            CollectionTraitInfo::VecBox(t) => StructField::VecBox(t),
-            CollectionTraitInfo::VecRc(t) => StructField::VecRc(t),
-            CollectionTraitInfo::VecArc(t) => StructField::VecArc(t),
-            CollectionTraitInfo::HashMapBox(k, t) => StructField::HashMapBox(k, t),
-            CollectionTraitInfo::HashMapRc(k, t) => StructField::HashMapRc(k, t),
-            CollectionTraitInfo::HashMapArc(k, t) => StructField::HashMapArc(k, t),
-        };
-    }
-    if is_forward_field(ty) {
-        return StructField::Forward;
-    }
-    if let Some((_, _)) = is_box_dyn_trait(ty) {
-        return StructField::BoxDyn;
-    }
-    if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
-        return StructField::RcDyn(trait_name);
-    }
-    if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
-        return StructField::ArcDyn(trait_name);
-    }
-    if contains_trait_object(ty) {
-        return StructField::ContainsTraitObject;
-    }
-    StructField::None
-}
-
-#[derive(Debug)]
-pub(super) struct TypeNode {
-    /// Simple type name, used for type matching (e.g., "LeaderId", "Vec")
-    pub name: String,
-    /// Full type path string, used for code generation (e.g., "C::LeaderId")
-    pub full_path: String,
-    pub generics: Vec<TypeNode>,
-    /// For arrays, store the original type string "[T; N]" to preserve length info
-    pub original_type_str: Option<String>,
-}
-
-pub(super) fn try_primitive_vec_type(node: &TypeNode) -> Option<TokenStream> {
-    if node.name != "Vec" {
-        return None;
-    }
-    let child = node.generics.first()?;
-    for (ty_name, type_id, _) in PRIMITIVE_ARRAY_TYPE_MAP {
-        if child.name == *ty_name {
-            return Some(quote! { #type_id });
-        }
-    }
-    None
-}
-
-pub(super) fn try_vec_of_option_primitive(node: &TypeNode) -> Option<TokenStream> {
-    if node.name != "Vec" {
-        return None;
-    }
-    let child = node.generics.first()?;
-    if child.name != "Option" {
-        return None;
-    }
-    let grandchild = child.generics.first()?;
-    for (ty_name, _, _) in PRIMITIVE_ARRAY_TYPE_MAP {
-        if grandchild.name == *ty_name {
-            return Some(quote! {
-                compile_error!("Vec<Option<primitive>> is not allowed!");
-            });
-        }
-    }
-
-    None
-}
-
-impl fmt::Display for TypeNode {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if self.name == "Tuple" {
-            // Format as Rust tuple syntax: (T1, T2, T3)
-            write!(
-                f,
-                "({})",
-                self.generics
-                    .iter()
-                    .map(|g| g.to_string())
-                    .collect::<Vec<_>>()
-                    .join(", ")
-            )
-        } else if self.name == "Array" {
-            // For arrays, use the original type string if available (e.g., "[f32; 4]")
-            if let Some(ref original) = self.original_type_str {
-                write!(f, "{}", original)
-            } else {
-                // Fallback - shouldn't happen if properly constructed
-                write!(f, "Array")
-            }
-        } else if self.generics.is_empty() {
-            // Use full_path to preserve associated type paths like C::LeaderId
-            write!(f, "{}", self.full_path)
-        } else {
-            // Use full_path for the base type, recursively format generics
-            write!(
-                f,
-                "{}<{}>",
-                self.full_path,
-                self.generics
-                    .iter()
-                    .map(|g| g.to_string())
-                    .collect::<Vec<_>>()
-                    .join(",")
-            )
-        }
-    }
-}
-
-pub(super) fn extract_type_name(ty: &Type) -> String {
-    if let Type::Path(type_path) = ty {
-        type_path.path.segments.last().unwrap().ident.to_string()
-    } else if matches!(ty, Type::TraitObject(_)) {
-        "TraitObject".to_string()
-    } else if matches!(ty, Type::Tuple(_)) {
-        "Tuple".to_string()
-    } else if matches!(ty, Type::Array(_)) {
-        "Array".to_string()
-    } else {
-        quote!(#ty).to_string()
-    }
-}
-
-/// Extracts the full type path string, preserving associated types like `C::LeaderId`
-pub(super) fn extract_full_type_path(ty: &Type) -> String {
-    if let Type::Path(type_path) = ty {
-        // Build the full path from all segments without generic arguments
-        type_path
-            .path
-            .segments
-            .iter()
-            .map(|seg| seg.ident.to_string())
-            .collect::<Vec<_>>()
-            .join("::")
-    } else if matches!(ty, Type::TraitObject(_)) {
-        "TraitObject".to_string()
-    } else if matches!(ty, Type::Tuple(_)) {
-        "Tuple".to_string()
-    } else if matches!(ty, Type::Array(_)) {
-        "Array".to_string()
-    } else {
-        quote!(#ty).to_string()
-    }
-}
-
-#[allow(dead_code)]
-pub(super) fn is_option(ty: &Type) -> bool {
-    if let Type::Path(type_path) = ty {
-        if let Some(seg) = type_path.path.segments.last() {
-            if seg.ident == "Option" {
-                return true;
-            }
-        }
-    }
-    false
-}
-
-pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
-    // Handle trait objects specially - they can't be parsed as normal types
-    if matches!(ty, Type::TraitObject(_)) {
-        return TypeNode {
-            name: "TraitObject".to_string(),
-            full_path: "TraitObject".to_string(),
-            generics: vec![],
-            original_type_str: None,
-        };
-    }
-
-    // Handle tuples - make child generics empty
-    if let Type::Tuple(_tuple) = ty {
-        return TypeNode {
-            name: "Tuple".to_string(),
-            full_path: "Tuple".to_string(),
-            generics: vec![],
-            original_type_str: None,
-        };
-    }
-
-    // Handle arrays - extract element type and preserve original type string
-    if let Type::Array(array) = ty {
-        let elem_node = parse_generic_tree(&array.elem);
-        // Preserve the original type string including the length (e.g., "[f32; 4]")
-        let original_type_str = quote!(#ty).to_string().replace(' ', "");
-        return TypeNode {
-            name: "Array".to_string(),
-            full_path: "Array".to_string(),
-            generics: vec![elem_node],
-            original_type_str: Some(original_type_str),
-        };
-    }
-
-    let name = extract_type_name(ty);
-    let full_path = extract_full_type_path(ty);
-
-    let generics = if let Type::Path(type_path) = ty {
-        if let PathArguments::AngleBracketed(args) =
-            &type_path.path.segments.last().unwrap().arguments
-        {
-            args.args
-                .iter()
-                .filter_map(|arg| {
-                    if let GenericArgument::Type(ty) = arg {
-                        Some(parse_generic_tree(ty))
-                    } else {
-                        None
-                    }
-                })
-                .collect()
-        } else {
-            vec![]
-        }
-    } else {
-        vec![]
-    };
-    TypeNode {
-        name,
-        full_path,
-        generics,
-        original_type_str: None,
-    }
-}
-
-pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> TokenStream {
-    // Special handling for type parameters (e.g., C, T, E)
-    // Type parameters should use UNKNOWN type ID since they are not concrete types.
-    // This prevents `C::LeaderId` generating code like `<C as Serializer>::fory_get_type_id()` which
-    // would require the type parameter to implement Serializer.
-    if is_type_parameter(&node.name) {
-        return quote! {
-            fory_core::meta::FieldType::new(
-                fory_core::type_id::TypeId::UNKNOWN as u32,
-                true,
-                vec![]
-            )
-        };
-    }
-
-    // Special handling for tuples: always use FieldType { LIST, nullable: true, generics: vec![UNKNOWN] }
-    if node.name == "Tuple" {
-        return quote! {
-            fory_core::meta::FieldType::new(
-                fory_core::type_id::TypeId::LIST as u32,
-                true,
-                vec![fory_core::meta::FieldType::new(
-                    fory_core::type_id::TypeId::UNKNOWN as u32,
-                    true,
-                    vec![]
-                )]
-            )
-        };
-    }
-
-    // Special handling for arrays: treat them as lists with element type generic
-    if node.name == "Array" {
-        if let Some(elem_node) = node.generics.first() {
-            let elem_token = generic_tree_to_tokens(elem_node);
-            // Check if element is primitive to determine the correct type ID
-            let is_primitive_elem = PRIMITIVE_TYPE_NAMES.contains(&elem_node.name.as_str());
-            if is_primitive_elem {
-                // For primitive arrays, use primitive array type ID
-                let type_id_token = match elem_node.name.as_str() {
-                    "bool" => quote! { fory_core::type_id::TypeId::BOOL_ARRAY as u32 },
-                    "i8" => quote! { fory_core::type_id::TypeId::INT8_ARRAY as u32 },
-                    "i16" => quote! { fory_core::type_id::TypeId::INT16_ARRAY as u32 },
-                    "i32" => quote! { fory_core::type_id::TypeId::INT32_ARRAY as u32 },
-                    "i64" => quote! { fory_core::type_id::TypeId::INT64_ARRAY as u32 },
-                    "i128" => quote! { fory_core::type_id::TypeId::INT128_ARRAY as u32 },
-                    "float16" | "Float16" => {
-                        quote! { fory_core::type_id::TypeId::FLOAT16_ARRAY as u32 }
-                    }
-                    "bfloat16" | "BFloat16" => {
-                        quote! { fory_core::type_id::TypeId::BFLOAT16_ARRAY as u32 }
-                    }
-                    "f32" => quote! { fory_core::type_id::TypeId::FLOAT32_ARRAY as u32 },
-                    "f64" => quote! { fory_core::type_id::TypeId::FLOAT64_ARRAY as u32 },
-                    "u8" => quote! { fory_core::type_id::TypeId::BINARY as u32 },
-                    "u16" => quote! { fory_core::type_id::TypeId::UINT16_ARRAY as u32 },
-                    "u32" => quote! { fory_core::type_id::TypeId::UINT32_ARRAY as u32 },
-                    "u64" => quote! { fory_core::type_id::TypeId::UINT64_ARRAY as u32 },
-                    "u128" => quote! { fory_core::type_id::TypeId::U128_ARRAY as u32 },
-                    _ => quote! { fory_core::type_id::TypeId::LIST as u32 },
-                };
-                return quote! {
-                    fory_core::meta::FieldType::new(
-                        #type_id_token,
-                        false,
-                        vec![]
-                    )
-                };
-            } else {
-                // For non-primitive arrays, use LIST type ID with element type as generic
-                return quote! {
-                    fory_core::meta::FieldType::new(
-                        fory_core::type_id::TypeId::LIST as u32,
-                        false,
-                        vec![#elem_token]
-                    )
-                };
-            }
-        } else {
-            // Array without element type info - shouldn't happen
-            return quote! { compile_error!("Array missing element type"); };
-        }
-    }
-
-    // If Option, unwrap it before generating children
-    let (nullable, base_node) = if node.name == "Option" {
-        if let Some(inner) = node.generics.first() {
-            if inner.name == "Option" {
-                return quote! { compile_error!("Nested adjacent Option is not allowed!"); };
-            }
-            // Special handling for Option<Tuple>
-            if inner.name == "Tuple" {
-                return quote! {
-                    fory_core::meta::FieldType::new(
-                        fory_core::type_id::TypeId::LIST as u32,
-                        true,
-                        vec![fory_core::meta::FieldType::new(
-                            fory_core::type_id::TypeId::UNKNOWN as u32,
-                            true,
-                            vec![]
-                        )]
-                    )
-                };
-            }
-            // Unwrap Option and propagate parsing
-            (true, inner)
-        } else {
-            return quote! { compile_error!("Missing Option inner type"); };
-        }
-    } else {
-        (!PRIMITIVE_TYPE_NAMES.contains(&node.name.as_str()), node)
-    };
-
-    // If Rc or Arc, unwrap to inner type - these are reference wrappers
-    // that don't add type info to the field type (handled by track_ref flag)
-    let base_node = if base_node.name == "Rc" || base_node.name == "Arc" {
-        if let Some(inner) = base_node.generics.first() {
-            inner
-        } else {
-            base_node
-        }
-    } else {
-        base_node
-    };
-
-    // `Vec<Option<primitive>>` rule stays as is
-    if let Some(ts) = try_vec_of_option_primitive(base_node) {
-        return ts;
-    }
-
-    // Try primitive Vec type
-    let primitive_vec = try_primitive_vec_type(base_node);
-
-    // Recursively generate children token streams
-    let children_tokens: Vec<TokenStream> = if primitive_vec.is_none() {
-        base_node
-            .generics
-            .iter()
-            .map(generic_tree_to_tokens)
-            .collect()
-    } else {
-        vec![]
-    };
-
-    // Build the syn::Type from the DISPLAY of base_node, not the original node if Option
-    let ty: syn::Type = syn::parse_str(&base_node.to_string()).unwrap();
-
-    // Get type ID
-    let get_type_id = if let Some(ts) = primitive_vec {
-        ts
-    } else {
-        quote! {
-            <#ty as fory_core::serializer::Serializer>::fory_get_type_id(type_resolver)? as u32
-        }
-    };
-
-    quote! {
-        {
-            let mut type_id = #get_type_id;
-            let mut user_type_id = u32::MAX;
-            let mut generics = vec![#(#children_tokens),*] as Vec<fory_core::meta::FieldType>;
-            // For tuples and sets, if no generic info is available, add UNKNOWN element
-            // This handles type aliases to tuples where we can't detect the tuple at macro time
-            if (type_id == fory_core::type_id::TypeId::LIST as u32
-                || type_id == fory_core::type_id::TypeId::SET as u32)
-                && generics.is_empty() {
-                generics.push(fory_core::meta::FieldType::new(
-                    fory_core::type_id::TypeId::UNKNOWN as u32,
-                    true,
-                    vec![]
-                ));
-            }
-            let is_custom = !fory_core::type_id::is_internal_type(type_id);
-            if is_custom {
-                let type_info = <#ty as fory_core::serializer::Serializer>::fory_get_type_info(type_resolver)?;
-                type_id = type_info.get_type_id() as u32;
-                user_type_id = type_info.get_user_type_id();
-                if type_id == fory_core::type_id::TypeId::TYPED_UNION as u32
-                    || type_id == fory_core::type_id::TypeId::NAMED_UNION as u32 {
-                    type_id = fory_core::type_id::TypeId::UNION as u32;
-                    user_type_id = u32::MAX;
-                }
-                if type_resolver.is_xlang() && generics.len() > 0 {
-                    return Err(fory_core::error::Error::unsupported("serialization of generic structs and enums is not supported in xlang mode"));
-                } else {
-                    generics = vec![];
-                }
-            } else if type_id == fory_core::type_id::TypeId::TYPED_UNION as u32
-                || type_id == fory_core::type_id::TypeId::NAMED_UNION as u32 {
-                type_id = fory_core::type_id::TypeId::UNION as u32;
-            }
-            fory_core::meta::FieldType {
-                type_id,
-                user_type_id,
-                nullable: #nullable,
-                track_ref: false,
-                generics,
-            }
-        }
-    }
-}
-
 type FieldGroup = Vec<(String, String, u32)>;
 type FieldGroups = (
     FieldGroup,
@@ -732,225 +216,6 @@ fn get_primitive_type_id(ty: &str) -> u32 {
     }
 }
 
-pub(super) fn is_primitive_type(ty: &str) -> bool {
-    PRIMITIVE_TYPE_NAMES.contains(&ty)
-}
-
-/// Mapping of primitive type names to their writer and reader method names
-/// Order: (type_name, writer_method, reader_method)
-static PRIMITIVE_IO_METHODS: &[(&str, &str, &str)] = &[
-    ("bool", "write_bool", "read_bool"),
-    ("i8", "write_i8", "read_i8"),
-    ("i16", "write_i16", "read_i16"),
-    ("i32", "write_var_i32", "read_var_i32"),
-    ("i64", "write_var_i64", "read_var_i64"),
-    ("f32", "write_f32", "read_f32"),
-    ("f64", "write_f64", "read_f64"),
-    ("u8", "write_u8", "read_u8"),
-    ("u16", "write_u16", "read_u16"),
-    ("u32", "write_u32", "read_u32"),
-    ("u64", "write_u64", "read_u64"),
-    ("usize", "write_usize", "read_usize"),
-    ("u128", "write_u128", "read_u128"),
-];
-
-/// Check if a type is a direct primitive type (numeric or String, not wrapped in Option, Vec, etc.)
-pub(super) fn is_direct_primitive_type(ty: &Type) -> bool {
-    if let Type::Path(type_path) = ty {
-        if let Some(seg) = type_path.path.segments.last() {
-            // Check if it's a simple type path without generics
-            if matches!(seg.arguments, PathArguments::None) {
-                let type_name = seg.ident.to_string();
-                // Check for String type
-                if type_name == "String" {
-                    return true;
-                }
-                // Check for numeric primitive types
-                return PRIMITIVE_IO_METHODS
-                    .iter()
... 4702 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org