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 70d40a142 feat(rust): support rust tuple serialization (#2858)
70d40a142 is described below

commit 70d40a142b6b2d3d60a0643507694fdf6f4d2c70
Author: Shawn Yang <[email protected]>
AuthorDate: Sat Nov 1 21:32:41 2025 +0800

    feat(rust): support rust tuple serialization (#2858)
    
    ## What does this PR do?
    
    support rust tuple serialization:
    - Compatible mode
    - Consistent mode
    
    Protocol:
    - tuple are taken as LIST for serialization for xlang or compatible mode
    - write one by one directly for rust namtive no-compatible mode with no
    ref/type meta.
      - For option/pointer/trait, wirte ref meta, type meta then data
      - for others, only write data
    
    Core changes:
    - Use a macro to generate generic tuple serializer for tuple1~20.
    - And update ForyObject macro to support tuple
    - Make skip.rs support skip `list/set/map` whose generics is not the
    declared generics
    - Refactor  skip.rs  to make it more readable and faster
    
    ## Related issues
    Closes #2863
    Closes #2862
    
    ## Does this PR introduce any user-facing change?
    
    <!--
    If any user-facing interface changes, please [open an
    issue](https://github.com/apache/fory/issues/new/choose) describing the
    need to do so and update the document if necessary.
    
    Delete section if not applicable.
    -->
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    <!--
    When the PR has an impact on performance (if you don't know whether the
    PR will have an impact on performance, you can submit the PR first, and
    if it will have impact on performance, the code reviewer will explain
    it), be sure to attach a benchmark data here.
    
    Delete section if not applicable.
    -->
---
 AGENTS.md                                   |    2 -
 rust/README.md                              |   35 +-
 rust/fory-core/src/buffer.rs                |    8 +-
 rust/fory-core/src/meta/type_meta.rs        |   18 +-
 rust/fory-core/src/serializer/collection.rs |    2 +-
 rust/fory-core/src/serializer/map.rs        |    4 +-
 rust/fory-core/src/serializer/mod.rs        |    1 +
 rust/fory-core/src/serializer/skip.rs       |  608 ++++++++++-----
 rust/fory-core/src/serializer/tuple.rs      |  451 +++++++++++
 rust/fory-core/src/serializer/util.rs       |   20 +-
 rust/fory-core/src/types.rs                 |   15 +-
 rust/fory-derive/src/object/read.rs         |   30 +-
 rust/fory-derive/src/object/util.rs         |   79 +-
 rust/fory-derive/src/object/write.rs        |    8 +-
 rust/fory/src/lib.rs                        |   43 +-
 rust/tests/tests/mod.rs                     |    1 +
 rust/tests/tests/test_tuple.rs              |  346 +++++++++
 rust/tests/tests/test_tuple_compatible.rs   | 1068 +++++++++++++++++++++++++++
 18 files changed, 2496 insertions(+), 243 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 4c5772a21..d1468a686 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -125,8 +125,6 @@ go generate ./...
 - All changes to `rust` must pass the clippy check and tests.
 - You must set `RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1` when debuging rust 
tests to get backtrace.
 - You must not set `FORY_PANIC_ON_ERROR=1` when runing all rust tests to check 
whether all tests pass, some tests will check Error content, which will fail if 
error just panic.
-- When making changes to enum discriminant values in Rust, always run `cargo 
clean` to ensure no stale build artifacts
-  remain.
 
 ```bash
 # Check code
diff --git a/rust/README.md b/rust/README.md
index a1128522a..ca48cbb3c 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -589,7 +589,40 @@ let decoded: Status = fory.deserialize(&bytes)?;
 assert_eq!(status, decoded);
 ```
 
-### 6. Custom Serializers
+### 6. Tuple Support
+
+Apache Fory™ supports tuples up to 22 elements out of the box with efficient 
serialization in both compatible and non-compatible modes.
+
+**Features:**
+
+- Automatic serialization for tuples from 1 to 22 elements
+- Heterogeneous type support (each element can be a different type)
+- Schema evolution in Compatible mode (handles missing/extra elements)
+
+**Serialization modes:**
+
+1. **Non-compatible mode**: Serializes elements sequentially without 
collection headers for minimal overhead
+2. **Compatible mode**: Uses collection protocol with type metadata for schema 
evolution
+
+```rust
+use fory::{Fory, Error};
+
+let mut fory = Fory::default();
+
+// Tuple with heterogeneous types
+let data: (i32, String, bool, Vec<i32>) = (
+    42,
+    "hello".to_string(),
+    true,
+    vec![1, 2, 3],
+);
+
+let bytes = fory.serialize(&data)?;
+let decoded: (i32, String, bool, Vec<i32>) = fory.deserialize(&bytes)?;
+assert_eq!(data, decoded);
+```
+
+### 7. Custom Serializers
 
 For types that don't support `#[derive(ForyObject)]`, implement the 
`Serializer` trait manually. This is useful for:
 
diff --git a/rust/fory-core/src/buffer.rs b/rust/fory-core/src/buffer.rs
index 84a7739bc..b4a658268 100644
--- a/rust/fory-core/src/buffer.rs
+++ b/rust/fory-core/src/buffer.rs
@@ -410,12 +410,18 @@ impl<'a> Reader<'a> {
     }
 
     #[inline(always)]
-    pub fn read_u8_uncheck(&mut self) -> u8 {
+    fn read_u8_uncheck(&mut self) -> u8 {
         let result = unsafe { self.bf.get_unchecked(self.cursor) };
         self.move_next(1);
         *result
     }
 
+    #[inline(always)]
+    pub fn peek_u8(&mut self) -> Result<u8, Error> {
+        let result = self.value_at(self.cursor)?;
+        Ok(result)
+    }
+
     #[inline(always)]
     pub fn read_u8(&mut self) -> Result<u8, Error> {
         let result = self.value_at(self.cursor)?;
diff --git a/rust/fory-core/src/meta/type_meta.rs 
b/rust/fory-core/src/meta/type_meta.rs
index 8ef3019db..387e33b37 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -86,14 +86,20 @@ impl FieldType {
         writer.write_varuint32(header);
         match self.type_id {
             x if x == TypeId::LIST as u32 || x == TypeId::SET as u32 => {
-                let generic = self.generics.first().unwrap();
-                generic.to_bytes(writer, true, generic.nullable)?;
+                if let Some(generic) = self.generics.first() {
+                    generic.to_bytes(writer, true, generic.nullable)?;
+                } else {
+                    let generic = FieldType::new(TypeId::UNKNOWN as u32, true, 
vec![]);
+                    generic.to_bytes(writer, true, generic.nullable)?;
+                }
             }
             x if x == TypeId::MAP as u32 => {
-                let key_generic = self.generics.first().unwrap();
-                let val_generic = self.generics.get(1).unwrap();
-                key_generic.to_bytes(writer, true, key_generic.nullable)?;
-                val_generic.to_bytes(writer, true, val_generic.nullable)?;
+                if let (Some(key_generic), Some(val_generic)) =
+                    (self.generics.first(), self.generics.get(1))
+                {
+                    key_generic.to_bytes(writer, true, key_generic.nullable)?;
+                    val_generic.to_bytes(writer, true, val_generic.nullable)?;
+                }
             }
             _ => {}
         }
diff --git a/rust/fory-core/src/serializer/collection.rs 
b/rust/fory-core/src/serializer/collection.rs
index cad0b5333..6ff63ad3a 100644
--- a/rust/fory-core/src/serializer/collection.rs
+++ b/rust/fory-core/src/serializer/collection.rs
@@ -27,7 +27,7 @@ const TRACKING_REF: u8 = 0b1;
 pub const HAS_NULL: u8 = 0b10;
 
 // Whether collection elements type is declare type.
-const DECL_ELEMENT_TYPE: u8 = 0b100;
+pub const DECL_ELEMENT_TYPE: u8 = 0b100;
 
 //  Whether collection elements type same.
 pub const IS_SAME_TYPE: u8 = 0b1000;
diff --git a/rust/fory-core/src/serializer/map.rs 
b/rust/fory-core/src/serializer/map.rs
index f3b2fd818..85bf803e5 100644
--- a/rust/fory-core/src/serializer/map.rs
+++ b/rust/fory-core/src/serializer/map.rs
@@ -29,10 +29,10 @@ const MAX_CHUNK_SIZE: u8 = 255;
 
 const TRACKING_KEY_REF: u8 = 0b1;
 pub const KEY_NULL: u8 = 0b10;
-const DECL_KEY_TYPE: u8 = 0b100;
+pub const DECL_KEY_TYPE: u8 = 0b100;
 const TRACKING_VALUE_REF: u8 = 0b1000;
 pub const VALUE_NULL: u8 = 0b10000;
-const DECL_VALUE_TYPE: u8 = 0b100000;
+pub const DECL_VALUE_TYPE: u8 = 0b100000;
 
 fn write_chunk_size(context: &mut WriteContext, header_offset: usize, size: 
u8) {
     context.writer.set_bytes(header_offset + 1, &[size]);
diff --git a/rust/fory-core/src/serializer/mod.rs 
b/rust/fory-core/src/serializer/mod.rs
index af07b8674..e98b7dfad 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -36,6 +36,7 @@ pub mod skip;
 mod string;
 pub mod struct_;
 pub mod trait_object;
+mod tuple;
 mod unsigned_number;
 pub mod util;
 pub mod weak;
diff --git a/rust/fory-core/src/serializer/skip.rs 
b/rust/fory-core/src/serializer/skip.rs
index c63df3218..1af4e5c6b 100644
--- a/rust/fory-core/src/serializer/skip.rs
+++ b/rust/fory-core/src/serializer/skip.rs
@@ -19,24 +19,13 @@ use crate::ensure;
 use crate::error::Error;
 use crate::meta::FieldType;
 use crate::resolver::context::ReadContext;
-use crate::serializer::collection::{HAS_NULL, IS_SAME_TYPE};
+use crate::serializer::collection::{DECL_ELEMENT_TYPE, HAS_NULL, IS_SAME_TYPE};
 use crate::serializer::util;
 use crate::serializer::Serializer;
-use crate::types::{RefFlag, TypeId, BASIC_TYPES, CONTAINER_TYPES};
+use crate::types;
+use crate::types::{is_user_type, RefFlag};
 use chrono::{NaiveDate, NaiveDateTime};
-
-macro_rules! basic_type_deserialize {
-    ($tid:expr, $context:expr; $(($ty:ty, $id:ident)),+ $(,)?) => {
-        $(
-            if $tid == TypeId::$id {
-                <$ty as Serializer>::fory_read_data($context)?;
-                return Ok(());
-            }
-        )+else {
-            unreachable!()
-        }
-    };
-}
+use std::rc::Rc;
 
 #[allow(unreachable_code)]
 pub fn skip_field_value(
@@ -44,16 +33,294 @@ pub fn skip_field_value(
     field_type: &FieldType,
     read_ref_flag: bool,
 ) -> Result<(), Error> {
-    skip_value(context, field_type, read_ref_flag, true)
+    skip_value(context, field_type, read_ref_flag, true, &None)
+}
+
+const UNKNOWN_FIELD_TYPE: FieldType = FieldType {
+    type_id: types::UNKNOWN,
+    nullable: true,
+    generics: vec![],
+};
+
+pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> 
Result<(), Error> {
+    // Handle ref flag first if needed
+    if read_ref_flag {
+        let ref_flag = context.reader.read_i8()?;
+        if ref_flag == (RefFlag::Null as i8) {
+            return Ok(());
+        }
+    }
+
+    // Now read the type ID
+    let mut type_id = context.reader.peek_u8()? as u32;
+    if !is_user_type(type_id) {
+        type_id = context.reader.read_varuint32()?;
+    }
+    let field_type = match type_id {
+        types::LIST | types::SET => FieldType {
+            type_id,
+            nullable: true,
+            generics: vec![UNKNOWN_FIELD_TYPE],
+        },
+        types::MAP => FieldType {
+            type_id,
+            nullable: true,
+            generics: vec![UNKNOWN_FIELD_TYPE, UNKNOWN_FIELD_TYPE],
+        },
+        _ => FieldType {
+            type_id,
+            nullable: true,
+            generics: vec![],
+        },
+    };
+    // Don't read ref flag again in skip_value since we already handled it
+    skip_value(context, &field_type, false, false, &None)
+}
+
+fn skip_collection(context: &mut ReadContext, field_type: &FieldType) -> 
Result<(), Error> {
+    let length = context.reader.read_varuint32()? as usize;
+    if length == 0 {
+        return Ok(());
+    }
+    let header = context.reader.read_u8()?;
+    let has_null = (header & HAS_NULL) != 0;
+    let is_same_type = (header & IS_SAME_TYPE) != 0;
+    let skip_ref_flag = is_same_type && !has_null;
+    let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+    let default_elem_type = field_type.generics.first().unwrap();
+    let (type_info, elem_field_type);
+    let elem_type = if is_same_type && !is_declared {
+        let type_info_rc = context.read_any_typeinfo()?;
+        elem_field_type = FieldType {
+            type_id: type_info_rc.get_type_id(),
+            nullable: has_null,
+            generics: vec![],
+        };
+        type_info = Some(type_info_rc);
+        &elem_field_type
+    } else {
+        type_info = None;
+        default_elem_type
+    };
+    context.inc_depth()?;
+    for _ in 0..length {
+        skip_value(context, elem_type, !skip_ref_flag, false, &type_info)?;
+    }
+    context.dec_depth();
+    Ok(())
+}
+
+fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), 
Error> {
+    let length = context.reader.read_varuint32()?;
+    if length == 0 {
+        return Ok(());
+    }
+    let mut len_counter = 0;
+    let default_key_type = field_type.generics.first().unwrap();
+    let default_value_type = field_type.generics.get(1).unwrap();
+    loop {
+        if len_counter == length {
+            break;
+        }
+        let header = context.reader.read_u8()?;
+        if header & crate::serializer::map::KEY_NULL != 0
+            && header & crate::serializer::map::VALUE_NULL != 0
+        {
+            len_counter += 1;
+            continue;
+        }
+        if header & crate::serializer::map::KEY_NULL != 0 {
+            // Read value type info if not declared
+            let value_declared = (header & 
crate::serializer::map::DECL_VALUE_TYPE) != 0;
+            let (value_type_info, value_field_type);
+            let value_type = if !value_declared {
+                let type_info = context.read_any_typeinfo()?;
+                value_field_type = FieldType {
+                    type_id: type_info.get_type_id(),
+                    nullable: true,
+                    generics: vec![],
+                };
+                value_type_info = Some(type_info);
+                &value_field_type
+            } else {
+                value_type_info = None;
+                default_value_type
+            };
+            context.inc_depth()?;
+            skip_value(context, value_type, false, false, &value_type_info)?;
+            context.dec_depth();
+            len_counter += 1;
+            continue;
+        }
+        if header & crate::serializer::map::VALUE_NULL != 0 {
+            // Read key type info if not declared
+            let key_declared = (header & 
crate::serializer::map::DECL_KEY_TYPE) != 0;
+            let (key_type_info, key_field_type);
+            let key_type = if !key_declared {
+                let type_info = context.read_any_typeinfo()?;
+                key_field_type = FieldType {
+                    type_id: type_info.get_type_id(),
+                    nullable: true,
+                    generics: vec![],
+                };
+                key_type_info = Some(type_info);
+                &key_field_type
+            } else {
+                key_type_info = None;
+                default_key_type
+            };
+            context.inc_depth()?;
+            skip_value(context, key_type, false, false, &key_type_info)?;
+            context.dec_depth();
+            len_counter += 1;
+            continue;
+        }
+        // Both key and value are non-null
+        let chunk_size = context.reader.read_u8()?;
+        let key_declared = (header & crate::serializer::map::DECL_KEY_TYPE) != 
0;
+        let value_declared = (header & 
crate::serializer::map::DECL_VALUE_TYPE) != 0;
+
+        // Read key type info if not declared
+        let (key_type_info, key_field_type);
+        let key_type = if !key_declared {
+            let type_info = context.read_any_typeinfo()?;
+            key_field_type = FieldType {
+                type_id: type_info.get_type_id(),
+                nullable: true,
+                generics: vec![],
+            };
+            key_type_info = Some(type_info);
+            &key_field_type
+        } else {
+            key_type_info = None;
+            default_key_type
+        };
+
+        // Read value type info if not declared
+        let (value_type_info, value_field_type);
+        let value_type = if !value_declared {
+            let type_info = context.read_any_typeinfo()?;
+            value_field_type = FieldType {
+                type_id: type_info.get_type_id(),
+                nullable: true,
+                generics: vec![],
+            };
+            value_type_info = Some(type_info);
+            &value_field_type
+        } else {
+            value_type_info = None;
+            default_value_type
+        };
+
+        context.inc_depth()?;
+        for _ in 0..chunk_size {
+            skip_value(context, key_type, false, false, &key_type_info)?;
+            skip_value(context, value_type, false, false, &value_type_info)?;
+        }
+        context.dec_depth();
+        len_counter += chunk_size as u32;
+    }
+    Ok(())
+}
+
+fn skip_struct(
+    context: &mut ReadContext,
+    type_id_num: u32,
+    type_info: &Option<Rc<crate::TypeInfo>>,
+) -> Result<(), Error> {
+    let type_info_value = if type_info.is_none() {
+        let remote_type_id = context.reader.read_varuint32()?;
+        ensure!(
+            type_id_num == remote_type_id,
+            Error::type_mismatch(type_id_num, remote_type_id)
+        );
+        let meta_index = context.reader.read_varuint32()?;
+        context.get_type_info_by_index(meta_index as usize)?
+    } else {
+        type_info.as_ref().unwrap()
+    };
+    let field_infos = 
type_info_value.get_type_meta().get_field_infos().to_vec();
+    context.inc_depth()?;
+    for field_info in field_infos.iter() {
+        let read_ref_flag = util::field_need_write_ref_into(
+            field_info.field_type.type_id,
+            field_info.field_type.nullable,
+        );
+        skip_value(context, &field_info.field_type, read_ref_flag, true, 
&None)?;
+    }
+    context.dec_depth();
+    Ok(())
+}
+
+fn skip_ext(
+    context: &mut ReadContext,
+    type_id_num: u32,
+    type_info: &Option<Rc<crate::TypeInfo>>,
+) -> Result<(), Error> {
+    let type_info_value = if type_info.is_none() {
+        let remote_type_id = context.reader.read_varuint32()?;
+        ensure!(
+            type_id_num == remote_type_id,
+            Error::type_mismatch(type_id_num, remote_type_id)
+        );
+        let meta_index = context.reader.read_varuint32()?;
+        context.get_type_info_by_index(meta_index as usize)?
+    } else {
+        type_info.as_ref().unwrap()
+    };
+    let type_resolver = context.get_type_resolver();
+    let type_meta = type_info_value.get_type_meta();
+    type_resolver
+        .get_ext_name_harness(type_meta.get_namespace(), 
type_meta.get_type_name())?
+        .get_read_data_fn()(context)?;
+    Ok(())
+}
+
+fn skip_user_struct(context: &mut ReadContext, _type_id_num: u32) -> 
Result<(), Error> {
+    let remote_type_id = context.reader.read_varuint32()?;
+    let meta_index = context.reader.read_varuint32()?;
+    let type_info = context.get_type_info_by_index(meta_index as usize)?;
+    let type_meta = type_info.get_type_meta();
+    ensure!(
+        type_meta.get_type_id() == remote_type_id,
+        Error::type_mismatch(type_meta.get_type_id(), remote_type_id)
+    );
+    let field_infos = type_meta.get_field_infos().to_vec();
+    context.inc_depth()?;
+    for field_info in field_infos.iter() {
+        let read_ref_flag = util::field_need_write_ref_into(
+            field_info.field_type.type_id,
+            field_info.field_type.nullable,
+        );
+        skip_value(context, &field_info.field_type, read_ref_flag, true, 
&None)?;
+    }
+    context.dec_depth();
+    Ok(())
+}
+
+fn skip_user_ext(context: &mut ReadContext, type_id_num: u32) -> Result<(), 
Error> {
+    let remote_type_id = context.reader.read_varuint32()?;
+    ensure!(
+        type_id_num == remote_type_id,
+        Error::type_mismatch(type_id_num, remote_type_id)
+    );
+    context.inc_depth()?;
+    let type_resolver = context.get_type_resolver();
+    type_resolver
+        .get_ext_harness(type_id_num)?
+        .get_read_data_fn()(context)?;
+    context.dec_depth();
+    Ok(())
 }
 
 // call when is_field && is_compatible_mode
 #[allow(unreachable_code)]
-pub fn skip_value(
+fn skip_value(
     context: &mut ReadContext,
     field_type: &FieldType,
     read_ref_flag: bool,
     _is_field: bool,
+    type_info: &Option<Rc<crate::TypeInfo>>,
 ) -> Result<(), Error> {
     if read_ref_flag {
         let ref_flag = context.reader.read_i8()?;
@@ -62,187 +329,132 @@ pub fn skip_value(
         }
     }
     let type_id_num = field_type.type_id;
-    match TypeId::try_from(type_id_num as i16) {
-        Ok(type_id) => {
-            if BASIC_TYPES.contains(&type_id) {
-                basic_type_deserialize!(type_id, context;
-                    (bool, BOOL),
-                    (i8, INT8),
-                    (i16, INT16),
-                    (i32, INT32),
-                    (i64, INT64),
-                    (f32, FLOAT32),
-                    (f64, FLOAT64),
-                    (String, STRING),
-                    (NaiveDate, LOCAL_DATE),
-                    (NaiveDateTime, TIMESTAMP),
-                    (Vec<u8>, BINARY),
-                    (Vec<bool> , BOOL_ARRAY),
-                    (Vec<i8> , INT8_ARRAY),
-                    (Vec<i16> , INT16_ARRAY),
-                    (Vec<i32> , INT32_ARRAY),
-                    (Vec<i64> , INT64_ARRAY),
-                    (Vec<f32>, FLOAT32_ARRAY),
-                    (Vec<f64> , FLOAT64_ARRAY),
-                    (u8, U8),
-                    (u16, U16),
-                    (u32, U32),
-                    (u64, U64),
-                    (Vec<u16> , U16_ARRAY),
-                    (Vec<u32> , U32_ARRAY),
-                    (Vec<u64> , U64_ARRAY),
-                );
-            } else if CONTAINER_TYPES.contains(&type_id) {
-                if type_id == TypeId::LIST || type_id == TypeId::SET {
-                    let length = context.reader.read_varuint32()? as usize;
-                    if length == 0 {
-                        return Ok(());
-                    }
-                    let header = context.reader.read_u8()?;
-                    let has_null = (header & HAS_NULL) != 0;
-                    let is_same_type = (header & IS_SAME_TYPE) != 0;
-                    let skip_ref_flag = is_same_type && !has_null;
-                    let elem_type = field_type.generics.first().unwrap();
-                    context.inc_depth()?;
-                    for _ in 0..length {
-                        skip_value(context, elem_type, !skip_ref_flag, false)?;
-                    }
-                    context.dec_depth();
-                } else if type_id == TypeId::MAP {
-                    let length = context.reader.read_varuint32()?;
-                    if length == 0 {
-                        return Ok(());
-                    }
-                    let mut len_counter = 0;
-                    let key_type = field_type.generics.first().unwrap();
-                    let value_type = field_type.generics.get(1).unwrap();
-                    loop {
-                        if len_counter == length {
-                            break;
-                        }
-                        let header = context.reader.read_u8()?;
-                        if header & crate::serializer::map::KEY_NULL != 0
-                            && header & crate::serializer::map::VALUE_NULL != 0
-                        {
-                            len_counter += 1;
-                            continue;
-                        }
-                        if header & crate::serializer::map::KEY_NULL != 0 {
-                            // value_type.nullable determines whether ref flag 
was written
-                            context.inc_depth()?;
-                            skip_value(context, value_type, false, false)?;
-                            context.dec_depth();
-                            len_counter += 1;
-                            continue;
-                        }
-                        if header & crate::serializer::map::VALUE_NULL != 0 {
-                            // key_type.nullable determines whether ref flag 
was written
-                            context.inc_depth()?;
-                            skip_value(context, key_type, false, false)?;
-                            context.dec_depth();
-                            len_counter += 1;
-                            continue;
-                        }
-                        let chunk_size = context.reader.read_u8()?;
-                        context.inc_depth()?;
-                        for _ in (0..chunk_size).enumerate() {
-                            // key_type.nullable determines whether ref flag 
was written
-                            skip_value(context, key_type, false, false)?;
-                            // value_type.nullable determines whether ref flag 
was written
-                            skip_value(context, value_type, false, false)?;
-                        }
-                        context.dec_depth();
-                        len_counter += chunk_size as u32;
-                    }
-                }
-                Ok(())
-            } else if type_id == TypeId::NAMED_ENUM {
-                let _ordinal = context.reader.read_varuint32()?;
-                Ok(())
-            } else if type_id == TypeId::NAMED_COMPATIBLE_STRUCT {
-                let remote_type_id = context.reader.read_varuint32()?;
-                ensure!(
-                    type_id_num == remote_type_id,
-                    Error::type_mismatch(type_id_num, remote_type_id)
-                );
-                let meta_index = context.reader.read_varuint32()?;
-                let type_info = context.get_type_info_by_index(meta_index as 
usize)?;
-                let field_infos = 
type_info.get_type_meta().get_field_infos().to_vec();
-                context.inc_depth()?;
-                for field_info in field_infos.iter() {
-                    let read_ref_flag = util::field_requires_ref_flag(
-                        field_info.field_type.type_id,
-                        field_info.field_type.nullable,
-                    );
-                    skip_value(context, &field_info.field_type, read_ref_flag, 
true)?;
-                }
-                context.dec_depth();
-                Ok(())
-            } else if type_id == TypeId::NAMED_EXT {
-                let remote_type_id = context.reader.read_varuint32()?;
-                ensure!(
-                    type_id_num == remote_type_id,
-                    Error::type_mismatch(type_id_num, remote_type_id)
-                );
-                let meta_index = context.reader.read_varuint32()?;
-                let type_info = context.get_type_info_by_index(meta_index as 
usize)?;
-                let type_resolver = context.get_type_resolver();
-                let type_meta = type_info.get_type_meta();
-                type_resolver
-                    .get_ext_name_harness(type_meta.get_namespace(), 
type_meta.get_type_name())?
-                    .get_read_data_fn()(context)?;
-                Ok(())
-            } else {
-                unreachable!("unimplemented type: {:?}", type_id);
-            }
-        }
-        Err(_) => {
-            let internal_id = type_id_num & 0xff;
-            const COMPATIBLE_STRUCT_ID: u32 = TypeId::COMPATIBLE_STRUCT as u32;
-            const EXT_ID: u32 = TypeId::EXT as u32;
-            const ENUM_ID: u32 = TypeId::ENUM as u32;
-            if internal_id == COMPATIBLE_STRUCT_ID {
-                let remote_type_id = context.reader.read_varuint32()?;
-                let meta_index = context.reader.read_varuint32()?;
-                let type_info = context.get_type_info_by_index(meta_index as 
usize)?;
-                let type_meta = type_info.get_type_meta();
-                ensure!(
-                    type_meta.get_type_id() == remote_type_id,
-                    Error::type_mismatch(type_meta.get_type_id(), 
remote_type_id)
-                );
-                let field_infos = type_meta.get_field_infos().to_vec();
-                context.inc_depth()?;
-                for field_info in field_infos.iter() {
-                    let read_ref_flag = util::field_requires_ref_flag(
-                        field_info.field_type.type_id,
-                        field_info.field_type.nullable,
-                    );
-                    skip_value(context, &field_info.field_type, read_ref_flag, 
true)?;
-                }
-                context.dec_depth();
-            } else if internal_id == ENUM_ID {
-                let _ordinal = context.reader.read_varuint32()?;
-                let _ordinalx = _ordinal;
-                println!("skip enum ordinal: {}", _ordinalx);
-            } else if internal_id == EXT_ID {
-                let remote_type_id = context.reader.read_varuint32()?;
-                ensure!(
-                    type_id_num == remote_type_id,
-                    Error::type_mismatch(type_id_num, remote_type_id)
-                );
-                context.inc_depth()?;
-                let type_resolver = context.get_type_resolver();
-                type_resolver
-                    .get_ext_harness(type_id_num)?
-                    .get_read_data_fn()(context)?;
-                context.dec_depth();
-            } else {
-                return Err(Error::type_error(format!(
-                    "Unknown type id: {}",
-                    type_id_num
-                )));
-            }
-            Ok(())
+
+    // Check if it's a user-defined type (high bits set, meaning type_id > 255)
+    if type_id_num > 255 {
+        let internal_id = type_id_num & 0xff;
+        if internal_id == types::COMPATIBLE_STRUCT {
+            return skip_user_struct(context, type_id_num);
+        } else if internal_id == types::ENUM {
+            let _ordinal = context.reader.read_varuint32()?;
+            return Ok(());
+        } else if internal_id == types::EXT {
+            return skip_user_ext(context, type_id_num);
+        } else {
+            return Err(Error::type_error(format!(
+                "Unknown type id: {}",
+                type_id_num
+            )));
+        }
+    }
+
+    // Match on built-in types
+    match type_id_num {
+        // Basic types
+        types::BOOL => {
+            <bool as Serializer>::fory_read_data(context)?;
+        }
+        types::INT8 => {
+            <i8 as Serializer>::fory_read_data(context)?;
+        }
+        types::INT16 => {
+            <i16 as Serializer>::fory_read_data(context)?;
+        }
+        types::INT32 => {
+            <i32 as Serializer>::fory_read_data(context)?;
+        }
+        types::INT64 => {
+            <i64 as Serializer>::fory_read_data(context)?;
+        }
+        types::FLOAT32 => {
+            <f32 as Serializer>::fory_read_data(context)?;
+        }
+        types::FLOAT64 => {
+            <f64 as Serializer>::fory_read_data(context)?;
+        }
+        types::STRING => {
+            <String as Serializer>::fory_read_data(context)?;
+        }
+        types::LOCAL_DATE => {
+            <NaiveDate as Serializer>::fory_read_data(context)?;
+        }
+        types::TIMESTAMP => {
+            <NaiveDateTime as Serializer>::fory_read_data(context)?;
+        }
+        types::BINARY => {
+            <Vec<u8> as Serializer>::fory_read_data(context)?;
+        }
+        types::BOOL_ARRAY => {
+            <Vec<bool> as Serializer>::fory_read_data(context)?;
+        }
+        types::INT8_ARRAY => {
+            <Vec<i8> as Serializer>::fory_read_data(context)?;
+        }
+        types::INT16_ARRAY => {
+            <Vec<i16> as Serializer>::fory_read_data(context)?;
+        }
+        types::INT32_ARRAY => {
+            <Vec<i32> as Serializer>::fory_read_data(context)?;
+        }
+        types::INT64_ARRAY => {
+            <Vec<i64> as Serializer>::fory_read_data(context)?;
+        }
+        types::FLOAT32_ARRAY => {
+            <Vec<f32> as Serializer>::fory_read_data(context)?;
+        }
+        types::FLOAT64_ARRAY => {
+            <Vec<f64> as Serializer>::fory_read_data(context)?;
+        }
+        types::U8 => {
+            <u8 as Serializer>::fory_read_data(context)?;
+        }
+        types::U16 => {
+            <u16 as Serializer>::fory_read_data(context)?;
+        }
+        types::U32 => {
+            <u32 as Serializer>::fory_read_data(context)?;
+        }
+        types::U64 => {
+            <u64 as Serializer>::fory_read_data(context)?;
+        }
+        types::U16_ARRAY => {
+            <Vec<u16> as Serializer>::fory_read_data(context)?;
+        }
+        types::U32_ARRAY => {
+            <Vec<u32> as Serializer>::fory_read_data(context)?;
+        }
+        types::U64_ARRAY => {
+            <Vec<u64> as Serializer>::fory_read_data(context)?;
+        }
+
+        // Container types
+        types::LIST | types::SET => {
+            return skip_collection(context, field_type);
+        }
+        types::MAP => {
+            return skip_map(context, field_type);
+        }
+
+        // Named types
+        types::NAMED_ENUM => {
+            let _ordinal = context.reader.read_varuint32()?;
+        }
+        types::NAMED_COMPATIBLE_STRUCT => {
+            return skip_struct(context, type_id_num, type_info);
+        }
+        types::NAMED_EXT => {
+            return skip_ext(context, type_id_num, type_info);
+        }
+        types::UNKNOWN => {
+            return skip_any_value(context, false);
+        }
+
+        _ => {
+            return Err(Error::type_error(format!(
+                "Unimplemented type id: {}",
+                type_id_num
+            )));
         }
     }
+    Ok(())
 }
diff --git a/rust/fory-core/src/serializer/tuple.rs 
b/rust/fory-core/src/serializer/tuple.rs
new file mode 100644
index 000000000..066e77f5f
--- /dev/null
+++ b/rust/fory-core/src/serializer/tuple.rs
@@ -0,0 +1,451 @@
+// 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 crate::error::Error;
+use crate::resolver::context::{ReadContext, WriteContext};
+use crate::resolver::type_resolver::TypeResolver;
+use crate::serializer::collection::{read_collection_type_info, 
write_collection_type_info};
+use crate::serializer::skip::skip_any_value;
+use crate::serializer::{ForyDefault, Serializer};
+use crate::types::TypeId;
+use std::mem;
+
+/// Helper function to write a tuple element based on its type characteristics.
+/// This handles the different serialization strategies for various element 
types.
+#[inline(always)]
+fn write_tuple_element<T: Serializer>(elem: &T, context: &mut WriteContext) -> 
Result<(), Error> {
+    if T::fory_is_option() || T::fory_is_shared_ref() || 
T::fory_static_type_id() == TypeId::UNKNOWN
+    {
+        // For Option, shared references, or unknown static types, use full 
write with ref tracking
+        elem.fory_write(context, true, false, false)
+    } else {
+        // For concrete types with known static type IDs, directly write data
+        elem.fory_write_data(context)
+    }
+}
+
+/// Helper function to read a tuple element based on its type characteristics.
+#[inline(always)]
+fn read_tuple_element<T: Serializer + ForyDefault>(
+    context: &mut ReadContext,
+    _has_generics: bool,
+) -> Result<T, Error> {
+    if T::fory_is_option() || T::fory_is_shared_ref() || 
T::fory_static_type_id() == TypeId::UNKNOWN
+    {
+        // For Option, shared references, or unknown static types, use full 
read with ref tracking
+        T::fory_read(context, true, false)
+    } else {
+        // For concrete types with known static type IDs, directly read data
+        T::fory_read_data(context)
+    }
+}
+
+impl<T0: Serializer + ForyDefault> Serializer for (T0,) {
+    #[inline(always)]
+    fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> 
{
+        if !context.is_compatible() && !context.is_xlang() {
+            // Non-compatible mode: write elements directly
+            write_tuple_element(&self.0, context)?;
+        } else {
+            // Compatible mode: use collection protocol (heterogeneous)
+            context.writer.write_varuint32(1);
+            let header = 0u8; // No IS_SAME_TYPE flag
+            context.writer.write_u8(header);
+            self.0.fory_write(context, true, true, false)?;
+        }
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+        write_collection_type_info(context, TypeId::LIST as u32)
+    }
+
+    #[inline(always)]
+    fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
+        if !context.is_compatible() && !context.is_xlang() {
+            // Non-compatible mode: read elements directly
+            let elem0 = read_tuple_element::<T0>(context, false)?;
+            Ok((elem0,))
+        } else {
+            // Compatible mode: read collection protocol (heterogeneous)
+            let len = context.reader.read_varuint32()?;
+            let _header = context.reader.read_u8()?;
+
+            let elem0 = if len > 0 {
+                T0::fory_read(context, true, true)?
+            } else {
+                T0::fory_default()
+            };
+
+            // Skip any extra elements beyond the first
+            for _ in 1..len {
+                skip_any_value(context, true)?;
+            }
+
+            Ok((elem0,))
+        }
+    }
+
+    #[inline(always)]
+    fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+        read_collection_type_info(context, TypeId::LIST as u32)
+    }
+
+    #[inline(always)]
+    fn fory_reserved_space() -> usize {
+        mem::size_of::<u32>()
+    }
+
+    #[inline(always)]
+    fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
+        Ok(TypeId::LIST as u32)
+    }
+
+    #[inline(always)]
+    fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
+        Ok(TypeId::LIST as u32)
+    }
+
+    #[inline(always)]
+    fn fory_static_type_id() -> TypeId {
+        TypeId::LIST
+    }
+
+    #[inline(always)]
+    fn fory_is_wrapper_type() -> bool
+    where
+        Self: Sized,
+    {
+        true
+    }
+
+    #[inline(always)]
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl<T0: ForyDefault> ForyDefault for (T0,) {
+    #[inline(always)]
+    fn fory_default() -> Self {
+        (T0::fory_default(),)
+    }
+}
+
+macro_rules! fory_tuple_field {
+    ($tuple:expr, T0) => {
+        $tuple.0
+    };
+    ($tuple:expr, T1) => {
+        $tuple.1
+    };
+    ($tuple:expr, T2) => {
+        $tuple.2
+    };
+    ($tuple:expr, T3) => {
+        $tuple.3
+    };
+    ($tuple:expr, T4) => {
+        $tuple.4
+    };
+    ($tuple:expr, T5) => {
+        $tuple.5
+    };
+    ($tuple:expr, T6) => {
+        $tuple.6
+    };
+    ($tuple:expr, T7) => {
+        $tuple.7
+    };
+    ($tuple:expr, T8) => {
+        $tuple.8
+    };
+    ($tuple:expr, T9) => {
+        $tuple.9
+    };
+    ($tuple:expr, T10) => {
+        $tuple.10
+    };
+    ($tuple:expr, T11) => {
+        $tuple.11
+    };
+    ($tuple:expr, T12) => {
+        $tuple.12
+    };
+    ($tuple:expr, T13) => {
+        $tuple.13
+    };
+    ($tuple:expr, T14) => {
+        $tuple.14
+    };
+    ($tuple:expr, T15) => {
+        $tuple.15
+    };
+    ($tuple:expr, T16) => {
+        $tuple.16
+    };
+    ($tuple:expr, T17) => {
+        $tuple.17
+    };
+    ($tuple:expr, T18) => {
+        $tuple.18
+    };
+    ($tuple:expr, T19) => {
+        $tuple.19
+    };
+    ($tuple:expr, T20) => {
+        $tuple.20
+    };
+    ($tuple:expr, T21) => {
+        $tuple.21
+    };
+    ($tuple:expr, T22) => {
+        $tuple.22
+    };
+    ($tuple:expr, T23) => {
+        $tuple.23
+    };
+    ($tuple:expr, T24) => {
+        $tuple.24
+    };
+    ($tuple:expr, T25) => {
+        $tuple.25
+    };
+    ($tuple:expr, T26) => {
+        $tuple.26
+    };
+    ($tuple:expr, T27) => {
+        $tuple.27
+    };
+    ($tuple:expr, T28) => {
+        $tuple.28
+    };
+    ($tuple:expr, T29) => {
+        $tuple.29
+    };
+    ($tuple:expr, T30) => {
+        $tuple.30
+    };
+    ($tuple:expr, T31) => {
+        $tuple.31
+    };
+    ($tuple:expr, T32) => {
+        $tuple.32
+    };
+    ($tuple:expr, T33) => {
+        $tuple.33
+    };
+    ($tuple:expr, T34) => {
+        $tuple.34
+    };
+    ($tuple:expr, T35) => {
+        $tuple.35
+    };
+    ($tuple:expr, T36) => {
+        $tuple.36
+    };
+    ($tuple:expr, T37) => {
+        $tuple.37
+    };
+    ($tuple:expr, T38) => {
+        $tuple.38
+    };
+    ($tuple:expr, T39) => {
+        $tuple.39
+    };
+    ($tuple:expr, T40) => {
+        $tuple.40
+    };
+}
+
+macro_rules! fory_tuple_count {
+    ($($name:ident),+ $(,)?) => {
+        0usize $(+ fory_tuple_count!(@one $name))*
+    };
+    (@one $name:ident) => { 1usize };
+}
+
+/// Macro to implement Serializer for tuples of various sizes.
+/// Fory supports tuples up to 22 elements, longer tuples are not allowed.
+///
+/// This handles two serialization modes:
+/// 1. Non-compatible mode: Write elements one by one without collection 
headers and type metadata
+/// 2. Compatible mode: Use full collection protocol with headers and type 
info (always heterogeneous)
+#[macro_export]
+macro_rules! impl_tuple_serializer {
+    // Multiple element tuples (2+)
+    ($T0:ident $(, $T:ident)+ $(,)?) => {
+        impl<$T0: Serializer + ForyDefault, $($T: Serializer + ForyDefault),*> 
Serializer for ($T0, $($T),*) {
+            #[inline(always)]
+            fn fory_write_data(&self, context: &mut WriteContext) -> 
Result<(), Error> {
+                if !context.is_compatible() && !context.is_xlang() {
+                    // Non-compatible mode: write elements directly one by one
+                    write_tuple_element(&self.0, context)?;
+                    $(
+                        write_tuple_element(&fory_tuple_field!(self, $T), 
context)?;
+                    )*
+                } else {
+                    // Compatible mode: use collection protocol (always 
heterogeneous)
+                    let len = fory_tuple_count!($T0, $($T),*);
+                    context.writer.write_varuint32(len as u32);
+
+                    // Write header without IS_SAME_TYPE flag
+                    let header = 0u8;
+                    context.writer.write_u8(header);
+
+                    // Write each element with its type info
+                    self.0.fory_write(context, true, true, false)?;
+                    $(
+                        fory_tuple_field!(self, $T).fory_write(context, true, 
true, false)?;
+                    )*
+                }
+                Ok(())
+            }
+
+            #[inline(always)]
+            fn fory_write_type_info(context: &mut WriteContext) -> Result<(), 
Error> {
+                write_collection_type_info(context, TypeId::LIST as u32)
+            }
+
+            #[inline(always)]
+            fn fory_read_data(context: &mut ReadContext) -> Result<Self, 
Error> {
+                if !context.is_compatible() && !context.is_xlang() {
+                    // Non-compatible mode: read elements directly
+                    let elem0 = read_tuple_element::<$T0>(context, false)?;
+                    $(
+                        #[allow(non_snake_case)]
+                        let $T = read_tuple_element::<$T>(context, false)?;
+                    )*
+                    Ok((elem0, $($T),*))
+                } else {
+                    // Compatible mode: read collection protocol (always 
heterogeneous)
+                    // Handle flexible length: use defaults for missing 
elements, skip extras
+                    let len = context.reader.read_varuint32()?;
+                    let _header = context.reader.read_u8()?;
+
+                    // Track how many elements we've read
+                    let mut index = 0u32;
+
+                    // Read first element or use default
+                    let elem0 = if index < len {
+                        index += 1;
+                        $T0::fory_read(context, true, true)?
+                    } else {
+                        $T0::fory_default()
+                    };
+
+                    // Read remaining elements or use defaults
+                    $(
+                        #[allow(non_snake_case)]
+                        let $T = if index < len {
+                            index += 1;
+                            $T::fory_read(context, true, true)?
+                        } else {
+                            $T::fory_default()
+                        };
+                    )*
+
+                    // Skip any extra elements beyond what we expect
+                    for _ in index..len {
+                        skip_any_value(context, true)?;
+                    }
+
+                    Ok((elem0, $($T),*))
+                }
+            }
+
+            #[inline(always)]
+            fn fory_read_type_info(context: &mut ReadContext) -> Result<(), 
Error> {
+                read_collection_type_info(context, TypeId::LIST as u32)
+            }
+
+            #[inline(always)]
+            fn fory_reserved_space() -> usize {
+                mem::size_of::<u32>() // Size for length
+            }
+
+            #[inline(always)]
+            fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
+                Ok(TypeId::LIST as u32)
+            }
+
+            #[inline(always)]
+            fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> 
{
+                Ok(TypeId::LIST as u32)
+            }
+
+            #[inline(always)]
+            fn fory_static_type_id() -> TypeId {
+                TypeId::LIST
+            }
+
+            #[inline(always)]
+            fn fory_is_wrapper_type() -> bool
+                where
+                    Self: Sized, {
+                true
+            }
+
+            #[inline(always)]
+            fn as_any(&self) -> &dyn std::any::Any {
+                self
+            }
+        }
+
+        impl<$T0: ForyDefault, $($T: ForyDefault),*> ForyDefault for ($T0, 
$($T),*) {
+            #[inline(always)]
+            fn fory_default() -> Self {
+                ($T0::fory_default(), $($T::fory_default()),*)
+            }
+        }
+    };
+}
+
+// Implement Serializer for tuples of size 2-22
+impl_tuple_serializer!(T0, T1);
+impl_tuple_serializer!(T0, T1, T2);
+impl_tuple_serializer!(T0, T1, T2, T3);
+impl_tuple_serializer!(T0, T1, T2, T3, T4);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, 
T13);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, 
T13, T14);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, 
T13, T14, T15);
+impl_tuple_serializer!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, 
T13, T14, T15, T16);
+impl_tuple_serializer!(
+    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, 
T17
+);
+impl_tuple_serializer!(
+    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, 
T17, T18
+);
+impl_tuple_serializer!(
+    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, 
T17, T18, T19
+);
+impl_tuple_serializer!(
+    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, 
T17, T18, T19, T20
+);
+impl_tuple_serializer!(
+    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, 
T17, T18, T19, T20,
+    T21
+);
diff --git a/rust/fory-core/src/serializer/util.rs 
b/rust/fory-core/src/serializer/util.rs
index 6f6b606a8..586f3552b 100644
--- a/rust/fory-core/src/serializer/util.rs
+++ b/rust/fory-core/src/serializer/util.rs
@@ -20,6 +20,7 @@ use crate::error::Error;
 use crate::resolver::context::{ReadContext, WriteContext};
 use crate::serializer::Serializer;
 use crate::types::TypeId;
+use crate::types::{is_user_type, ENUM, NAMED_ENUM};
 
 const NO_REF_FLAG_TYPE_IDS: [u32; 11] = [
     TypeId::BOOL as u32,
@@ -52,13 +53,24 @@ pub(crate) fn read_basic_type_info<T: Serializer>(context: 
&mut ReadContext) ->
 /// - For enums (ENUM/NAMED_ENUM), we should skip writing type info
 /// - For structs and ext types, we should write type info
 #[inline]
-pub fn should_skip_type_info_at_runtime(type_id: u32) -> bool {
-    let internal_type_id = (type_id & 0xff) as i8;
-    internal_type_id == TypeId::ENUM as i8 || internal_type_id == 
TypeId::NAMED_ENUM as i8
+pub fn field_need_read_type_info(type_id: u32) -> bool {
+    let internal_type_id = type_id & 0xff;
+    if internal_type_id == ENUM || internal_type_id == NAMED_ENUM {
+        return false;
+    }
+    is_user_type(internal_type_id)
+}
+
+pub fn field_need_write_type_info<T: Serializer>() -> bool {
+    let static_type_id = T::fory_static_type_id() as u32;
+    if static_type_id == ENUM || static_type_id == NAMED_ENUM {
+        return false;
+    }
+    is_user_type(static_type_id)
 }
 
 #[inline]
-pub fn field_requires_ref_flag(type_id: u32, nullable: bool) -> bool {
+pub fn field_need_write_ref_into(type_id: u32, nullable: bool) -> bool {
     if nullable {
         return true;
     }
diff --git a/rust/fory-core/src/types.rs b/rust/fory-core/src/types.rs
index a718683a2..430fe8ff8 100644
--- a/rust/fory-core/src/types.rs
+++ b/rust/fory-core/src/types.rs
@@ -288,7 +288,7 @@ pub fn is_internal_type(type_id: u32) -> bool {
 }
 
 #[inline(always)]
-pub fn need_to_write_type_for_field(type_id: TypeId) -> bool {
+pub(crate) fn need_to_write_type_for_field(type_id: TypeId) -> bool {
     matches!(
         type_id,
         TypeId::STRUCT
@@ -302,8 +302,17 @@ pub fn need_to_write_type_for_field(type_id: TypeId) -> 
bool {
 }
 
 #[inline(always)]
-pub fn is_container_type(type_id: TypeId) -> bool {
-    type_id == TypeId::LIST || type_id == TypeId::SET || type_id == TypeId::MAP
+pub(crate) fn is_user_type(type_id: u32) -> bool {
+    matches!(
+        type_id,
+        ENUM | NAMED_ENUM
+            | STRUCT
+            | COMPATIBLE_STRUCT
+            | NAMED_STRUCT
+            | NAMED_COMPATIBLE_STRUCT
+            | EXT
+            | NAMED_EXT
+    )
 }
 
 pub fn compute_field_hash(hash: u32, id: i16) -> u32 {
diff --git a/rust/fory-derive/src/object/read.rs 
b/rust/fory-derive/src/object/read.rs
index b7c4cde18..1bdb8eb11 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -193,8 +193,8 @@ pub fn gen_read_field(field: &Field, private_ident: &Ident) 
-> TokenStream {
             } else {
                 // Custom types (struct/enum/ext) - need runtime check for 
enums
                 quote! {
-                    let is_enum = <#ty as 
fory_core::Serializer>::fory_static_type_id() == fory_core::types::TypeId::ENUM;
-                    let #private_ident = <#ty as 
fory_core::Serializer>::fory_read(context, true, !is_enum)?;
+                    let need_type_info = 
fory_core::serializer::util::field_need_write_type_info::<#ty>();
+                    let #private_ident = <#ty as 
fory_core::Serializer>::fory_read(context, true, need_type_info)?;
                 }
             }
         }
@@ -357,16 +357,18 @@ fn gen_read_compatible_match_arm_body(field: &Field, 
var_name: &Ident) -> TokenS
             }
         }
         StructField::None => {
+            // Note: _base_ty is currently unused but kept for potential 
future use
             let _base_ty = match &ty {
-                Type::Path(type_path) => 
&type_path.path.segments.first().unwrap().ident,
-                _ => panic!("Unsupported type"),
+                Type::Path(type_path) => 
Some(&type_path.path.segments.first().unwrap().ident),
+                Type::Tuple(_) => None, // Tuples don't have a simple ident
+                _ => None,              // Other types also don't have a 
simple ident
             };
             let skip_type_info = should_skip_type_info_for_field(ty);
             let dec_by_option = need_declared_by_option(field);
             if skip_type_info {
                 if dec_by_option {
                     quote! {
-                        let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                        let read_ref_flag = 
fory_core::serializer::util::field_need_write_ref_into(
                             _field.field_type.type_id,
                             _field.field_type.nullable,
                         );
@@ -378,7 +380,7 @@ fn gen_read_compatible_match_arm_body(field: &Field, 
var_name: &Ident) -> TokenS
                     }
                 } else {
                     quote! {
-                        let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                        let read_ref_flag = 
fory_core::serializer::util::field_need_write_ref_into(
                             _field.field_type.type_id,
                             _field.field_type.nullable,
                         );
@@ -391,26 +393,26 @@ fn gen_read_compatible_match_arm_body(field: &Field, 
var_name: &Ident) -> TokenS
                 }
             } else if dec_by_option {
                 quote! {
-                    let skip_type_info = 
fory_core::serializer::util::should_skip_type_info_at_runtime(_field.field_type.type_id);
-                    let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                    let read_type_info = 
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
+                    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 {
-                        #var_name = Some(<#ty as 
fory_core::Serializer>::fory_read(context, true, !skip_type_info)?);
+                        #var_name = Some(<#ty as 
fory_core::Serializer>::fory_read(context, true, read_type_info)?);
                     } else {
                         #var_name = Some(<#ty as 
fory_core::Serializer>::fory_read_data(context)?);
                     }
                 }
             } else {
                 quote! {
-                    let skip_type_info = 
fory_core::serializer::util::should_skip_type_info_at_runtime(_field.field_type.type_id);
-                    let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                    let read_type_info = 
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
+                    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 {
-                        #var_name = <#ty as 
fory_core::Serializer>::fory_read(context, true, !skip_type_info)?;
+                        #var_name = <#ty as 
fory_core::Serializer>::fory_read(context, true, read_type_info)?;
                     } else {
                         #var_name = <#ty as 
fory_core::Serializer>::fory_read_data(context)?;
                     }
@@ -526,7 +528,7 @@ pub fn gen_read_compatible(fields: &[&Field]) -> 
TokenStream {
         quote! {
             _ => {
                 let field_type = &_field.field_type;
-                let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                let read_ref_flag = 
fory_core::serializer::util::field_need_write_ref_into(
                     field_type.type_id,
                     field_type.nullable,
                 );
@@ -550,7 +552,7 @@ pub fn gen_read_compatible(fields: &[&Field]) -> 
TokenStream {
         quote! {
             _ => {
                 let field_type = &_field.field_type;
-                let read_ref_flag = 
fory_core::serializer::util::field_requires_ref_flag(
+                let read_ref_flag = 
fory_core::serializer::util::field_need_write_ref_into(
                     field_type.type_id,
                     field_type.nullable,
                 );
diff --git a/rust/fory-derive/src/object/util.rs 
b/rust/fory-derive/src/object/util.rs
index 0c8b34066..50def2ca0 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -279,7 +279,18 @@ pub(super) fn try_vec_of_option_primitive(node: &TypeNode) 
-> Option<TokenStream
 
 impl fmt::Display for TypeNode {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if self.generics.is_empty() {
+        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.generics.is_empty() {
             write!(f, "{}", self.name)
         } else {
             write!(
@@ -301,6 +312,8 @@ pub(super) fn extract_type_name(ty: &Type) -> String {
         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 {
         quote!(#ty).to_string()
     }
@@ -327,6 +340,14 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
         };
     }
 
+    // Handle tuples - make child generics empty
+    if let Type::Tuple(_tuple) = ty {
+        return TypeNode {
+            name: "Tuple".to_string(),
+            generics: vec![],
+        };
+    }
+
     let name = extract_type_name(ty);
 
     let generics = if let Type::Path(type_path) = ty {
@@ -353,12 +374,41 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
 }
 
 pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> TokenStream {
+    // 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::types::TypeId::LIST as u32,
+                true,
+                vec![fory_core::meta::FieldType {
+                    type_id: fory_core::types::TypeId::UNKNOWN as u32,
+                    nullable: true,
+                    generics: vec![],
+                }]
+            )
+        };
+    }
+
     // 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::types::TypeId::LIST as u32,
+                        true,
+                        vec![fory_core::meta::FieldType {
+                            type_id: fory_core::types::TypeId::UNKNOWN as u32,
+                            nullable: true,
+                            generics: vec![],
+                        }]
+                    )
+                };
+            }
             // Unwrap Option and propagate parsing
             (true, inner)
         } else {
@@ -390,6 +440,7 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> 
TokenStream {
     // 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 {
@@ -399,11 +450,22 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> 
TokenStream {
     };
 
     quote! {
-        fory_core::meta::FieldType::new(
-            #get_type_id,
-            #nullable,
-            vec![#(#children_tokens),*] as Vec<fory_core::meta::FieldType>
-        )
+        {
+            let type_id = #get_type_id;
+            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::types::TypeId::LIST as u32
+                || type_id == fory_core::types::TypeId::SET as u32)
+                && generics.is_empty() {
+                generics.push(fory_core::meta::FieldType::new(
+                    fory_core::types::TypeId::UNKNOWN as u32,
+                    true,
+                    vec![]
+                ));
+            }
+            fory_core::meta::FieldType::new(type_id, #nullable, generics)
+        }
     }
 }
 
@@ -513,6 +575,11 @@ pub(crate) fn get_type_id_by_name(ty: &str) -> u32 {
         return TypeId::MAP as u32;
     }
 
+    // Check tuple types (represented as "Tuple" by extract_type_name or 
starts with '(')
+    if ty == "Tuple" || ty.starts_with('(') {
+        return TypeId::LIST as u32;
+    }
+
     // Unknown type
     TypeId::UNKNOWN as u32
 }
diff --git a/rust/fory-derive/src/object/write.rs 
b/rust/fory-derive/src/object/write.rs
index 257b9ec8c..fba08ced4 100644
--- a/rust/fory-derive/src/object/write.rs
+++ b/rust/fory-derive/src/object/write.rs
@@ -208,13 +208,13 @@ pub fn gen_write_field(field: &Field, ident: &Ident, 
use_self: bool) -> TokenStr
                     }
                 } else if skip_ref_flag {
                     quote! {
-                        let is_enum = <#ty as 
fory_core::Serializer>::fory_static_type_id() == fory_core::types::TypeId::ENUM;
-                        <#ty as fory_core::Serializer>::fory_write(&#value_ts, 
context, false, !is_enum, false)?;
+                        let need_type_info = 
fory_core::serializer::util::field_need_write_type_info::<#ty>();
+                        <#ty as fory_core::Serializer>::fory_write(&#value_ts, 
context, false, need_type_info, false)?;
                     }
                 } else {
                     quote! {
-                        let is_enum = <#ty as 
fory_core::Serializer>::fory_static_type_id() == fory_core::types::TypeId::ENUM;
-                        <#ty as fory_core::Serializer>::fory_write(&#value_ts, 
context, true, !is_enum, false)?;
+                        let need_type_info = 
fory_core::serializer::util::field_need_write_type_info::<#ty>();
+                        <#ty as fory_core::Serializer>::fory_write(&#value_ts, 
context, true, need_type_info, false)?;
                     }
                 }
             }
diff --git a/rust/fory/src/lib.rs b/rust/fory/src/lib.rs
index 7a7c82dcb..ee347e6c7 100644
--- a/rust/fory/src/lib.rs
+++ b/rust/fory/src/lib.rs
@@ -697,7 +697,48 @@
 //! # }
 //! ```
 //!
-//! ### 6. Custom Serializers
+//! ### 6. Tuple Support
+//!
+//! **What it does:** Supports tuples up to 22 elements with automatic 
heterogeneous type
+//! handling and schema evolution in compatible mode.
+//!
+//! **Why it matters:** Tuples provide lightweight aggregation without 
defining full structs,
+//! useful for temporary groupings, function return values, and ad-hoc data 
structures.
+//!
+//! **Technical approach:** Each tuple size (1-22) has a specialized 
`Serializer` implementation.
+//! In non-compatible mode, elements are serialized sequentially without 
overhead. In compatible
+//! mode, the tuple is serialized as a heterogeneous collection with type 
metadata for each element.
+//!
+//! **Features:**
+//!
+//! - Automatic serialization for tuples from 1 to 22 elements
+//! - Heterogeneous type support (each element can be a different type)
+//! - Schema evolution in Compatible mode (handles missing/extra elements)
+//! - Default values for missing elements during deserialization
+//!
+//! ```rust
+//! use fory::Fory;
+//! use fory::Error;
+//!
+//! # fn main() -> Result<(), Error> {
+//! let mut fory = Fory::default();
+//!
+//! // Tuple with heterogeneous types
+//! let data: (i32, String, bool, Vec<i32>) = (
+//!     42,
+//!     "hello".to_string(),
+//!     true,
+//!     vec![1, 2, 3],
+//! );
+//!
+//! let bytes = fory.serialize(&data)?;
+//! let decoded: (i32, String, bool, Vec<i32>) = fory.deserialize(&bytes)?;
+//! assert_eq!(data, decoded);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ### 7. Custom Serializers
 //!
 //! **What it does:** Allows manual implementation of the `Serializer` trait 
for types
 //! that don't support `#[derive(ForyObject)]`.
diff --git a/rust/tests/tests/mod.rs b/rust/tests/tests/mod.rs
index 5b26bc5f0..2f83762a5 100644
--- a/rust/tests/tests/mod.rs
+++ b/rust/tests/tests/mod.rs
@@ -19,3 +19,4 @@ mod compatible;
 mod test_any;
 mod test_collection;
 mod test_max_dyn_depth;
+mod test_tuple;
diff --git a/rust/tests/tests/test_tuple.rs b/rust/tests/tests/test_tuple.rs
new file mode 100644
index 000000000..858067a5a
--- /dev/null
+++ b/rust/tests/tests/test_tuple.rs
@@ -0,0 +1,346 @@
+// 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 fory_core::fory::Fory;
+use fory_derive::ForyObject;
+use std::rc::Rc;
+
+const PI_F64: f64 = std::f64::consts::PI;
+
+type ComplexNestedTuple = ((Vec<i32>, Option<String>), (Rc<bool>, (i32, f64)));
+
+// Test homogeneous tuples with primitive types
+#[test]
+fn test_homogeneous_tuple_i32() {
+    let fory = Fory::default();
+    let tuple = (1i32, 2i32, 3i32);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, i32, i32) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_homogeneous_tuple_f64() {
+    let fory = Fory::default();
+    let tuple = (1.5f64, 2.5f64, 3.5f64, 4.5f64);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (f64, f64, f64, f64) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_homogeneous_tuple_string() {
+    let fory = Fory::default();
+    let tuple = ("hello".to_string(), "world".to_string(), "fory".to_string());
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (String, String, String) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test heterogeneous tuples with different types
+#[test]
+fn test_heterogeneous_tuple_simple() {
+    let fory = Fory::default();
+    let tuple = (42i32, "hello".to_string());
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, String) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_heterogeneous_tuple_complex() {
+    let fory = Fory::default();
+    let tuple = (42i32, "hello".to_string(), PI_F64, true, vec![1, 2, 3]);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, String, f64, bool, Vec<i32>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test single element tuple
+#[test]
+fn test_single_element_tuple() {
+    let fory = Fory::default();
+    let tuple = (42i32,);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32,) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test tuples with Option types
+#[test]
+fn test_tuple_with_options() {
+    let fory = Fory::default();
+    let tuple = (Some(42i32), None::<i32>, Some(100i32));
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (Option<i32>, Option<i32>, Option<i32>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_heterogeneous_tuple_with_options() {
+    let fory = Fory::default();
+    let tuple = (Some(42i32), "hello".to_string(), None::<String>);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (Option<i32>, String, Option<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test tuples with collections
+#[test]
+fn test_tuple_with_vectors() {
+    let fory = Fory::default();
+    let tuple = (vec![1, 2, 3], vec![4, 5, 6]);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (Vec<i32>, Vec<i32>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_tuple_with_mixed_collections() {
+    let fory = Fory::default();
+    let tuple = (vec![1, 2, 3], vec!["a".to_string(), "b".to_string()]);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (Vec<i32>, Vec<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test nested tuples
+#[test]
+fn test_nested_tuples() {
+    let fory = Fory::default();
+    let tuple = ((1i32, 2i32), (3i32, 4i32));
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: ((i32, i32), (i32, i32)) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_deeply_nested_tuples() {
+    let fory = Fory::default();
+    let tuple = (1i32, (2i32, (3i32, 4i32)));
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, (i32, (i32, i32))) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test large tuples
+#[test]
+fn test_large_homogeneous_tuple() {
+    let fory = Fory::default();
+    let tuple = (
+        1i32, 2i32, 3i32, 4i32, 5i32, 6i32, 7i32, 8i32, 9i32, 10i32, 11i32, 
12i32,
+    );
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+#[test]
+fn test_large_heterogeneous_tuple() {
+    let fory = Fory::default();
+    let tuple = (
+        1i32,
+        2i64,
+        3u32,
+        4u64,
+        5.0f32,
+        6.0f64,
+        "seven".to_string(),
+        true,
+    );
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (i32, i64, u32, u64, f32, f64, String, bool) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test tuples with Rc/Arc (shared references)
+#[test]
+fn test_tuple_with_rc() {
+    let fory = Fory::default();
+    let value = Rc::new(42i32);
+    let tuple = (Rc::clone(&value), Rc::clone(&value));
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (Rc<i32>, Rc<i32>) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*obj.0, 42);
+    assert_eq!(*obj.1, 42);
+    // Note: deserialization creates independent Rc instances, not shared ones
+}
+
+// Test tuples with bool
+#[test]
+fn test_homogeneous_tuple_bool() {
+    let fory = Fory::default();
+    let tuple = (true, false, true, false);
+    let bin = fory.serialize(&tuple).unwrap();
+    let obj: (bool, bool, bool, bool) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple, obj);
+}
+
+// Test tuples with u8, u16, u32, u64
+#[test]
+fn test_homogeneous_tuple_unsigned() {
+    let fory = Fory::default();
+    let tuple_u8 = (1u8, 2u8, 3u8);
+    let bin = fory.serialize(&tuple_u8).unwrap();
+    let obj: (u8, u8, u8) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_u8, obj);
+
+    let tuple_u16 = (100u16, 200u16, 300u16);
+    let bin = fory.serialize(&tuple_u16).unwrap();
+    let obj: (u16, u16, u16) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_u16, obj);
+
+    let tuple_u32 = (1000u32, 2000u32, 3000u32);
+    let bin = fory.serialize(&tuple_u32).unwrap();
+    let obj: (u32, u32, u32) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_u32, obj);
+
+    let tuple_u64 = (10000u64, 20000u64, 30000u64);
+    let bin = fory.serialize(&tuple_u64).unwrap();
+    let obj: (u64, u64, u64) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_u64, obj);
+}
+
+// Test that tuples are serialized with LIST type ID
+#[test]
+fn test_tuple_type_id() {
+    use fory_core::serializer::Serializer;
+    use fory_core::types::TypeId;
+    assert_eq!(<(i32, i32)>::fory_static_type_id(), TypeId::LIST);
+    assert_eq!(<(i32, String)>::fory_static_type_id(), TypeId::LIST);
+    assert_eq!(<(i32,)>::fory_static_type_id(), TypeId::LIST);
+}
+
+// Test tuples in xlang mode
+#[test]
+fn test_tuple_xlang_mode() {
+    let fory = Fory::default().xlang(true);
+
+    // Test homogeneous tuple
+    let homogeneous = (1i32, 2i32, 3i32);
+    let bin = fory.serialize(&homogeneous).unwrap();
+    let obj: (i32, i32, i32) = fory.deserialize(&bin).expect("deserialize 
homogeneous");
+    assert_eq!(homogeneous, obj);
+
+    // Test heterogeneous tuple
+    let heterogeneous = (42i32, "hello".to_string(), PI_F64, true);
+    let bin = fory.serialize(&heterogeneous).unwrap();
+    let obj: (i32, String, f64, bool) = 
fory.deserialize(&bin).expect("deserialize heterogeneous");
+    assert_eq!(heterogeneous, obj);
+
+    // Test nested tuple
+    let nested = ((1i32, "inner".to_string()), (2.5f64, vec![1, 2, 3]));
+    let bin = fory.serialize(&nested).unwrap();
+    let obj: ((i32, String), (f64, Vec<i32>)) = 
fory.deserialize(&bin).expect("deserialize nested");
+    assert_eq!(nested, obj);
+
+    // Test tuple with Option
+    let with_option = (Some(42i32), None::<String>, Some(vec![1, 2]));
+    let bin = fory.serialize(&with_option).unwrap();
+    let obj: (Option<i32>, Option<String>, Option<Vec<i32>>) =
+        fory.deserialize(&bin).expect("deserialize with option");
+    assert_eq!(with_option, obj);
+}
+
+// Helper method for struct with simple tuple fields
+fn run_struct_with_simple_tuple_fields(xlang: bool) {
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct SimpleTupleStruct {
+        id: i32,
+        coordinates: (f64, f64),
+        pair: (String, i32),
+        triple: (bool, u32, i64),
+    }
+
+    let mut fory = Fory::default().xlang(xlang);
+    fory.register::<SimpleTupleStruct>(1).unwrap();
+
+    let data = SimpleTupleStruct {
+        id: 42,
+        coordinates: (10.5, 20.3),
+        pair: ("hello".to_string(), 100),
+        triple: (true, 200, -300),
+    };
+
+    // Serialize
+    let bytes = fory.serialize(&data).unwrap();
+    // Deserialize
+    let decoded: SimpleTupleStruct = fory.deserialize(&bytes).unwrap();
+    assert_eq!(data, decoded);
+}
+
+// Helper method for struct with complex tuple fields
+fn run_struct_with_complex_tuple_fields(xlang: bool) {
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct ComplexTupleStruct {
+        name: String,
+        // Heterogeneous tuple
+        heterogeneous: (i32, String, f64, bool),
+        // Nested tuples
+        nested: ((i32, String), (f64, bool)),
+        // Tuple with collection
+        with_collection: (Vec<i32>, Vec<String>),
+        // Tuple with Option
+        with_option: (Option<i32>, Option<String>),
+        // Tuple with Rc (shared_ptr)
+        with_rc: (Rc<i32>, Rc<String>),
+        // Complex nested tuple with collections and options
+        complex_nested: ComplexNestedTuple,
+    }
+
+    let mut fory = Fory::default().xlang(xlang);
+    fory.register::<ComplexTupleStruct>(2).unwrap();
+
+    let data = ComplexTupleStruct {
+        name: "Complex Test".to_string(),
+        heterogeneous: (42, "world".to_string(), PI_F64, true),
+        nested: ((100, "nested".to_string()), (2.71, false)),
+        with_collection: (vec![1, 2, 3], vec!["a".to_string(), 
"b".to_string()]),
+        with_option: (Some(999), None),
+        with_rc: (Rc::new(777), Rc::new("shared".to_string())),
+        complex_nested: (
+            (vec![10, 20, 30], Some("optional".to_string())),
+            (Rc::new(true), (42, 1.618)),
+        ),
+    };
+
+    // Serialize
+    let bytes = fory.serialize(&data).unwrap();
+    // Deserialize
+    let decoded: ComplexTupleStruct = fory.deserialize(&bytes).unwrap();
+    assert_eq!(data, decoded);
+}
+
+// Test struct with simple tuple fields (non-xlang mode)
+#[test]
+fn test_struct_with_simple_tuple_fields() {
+    run_struct_with_simple_tuple_fields(false);
+}
+
+// Test struct with complex tuple fields (non-xlang mode)
+#[test]
+fn test_struct_with_complex_tuple_fields() {
+    run_struct_with_complex_tuple_fields(false);
+}
+
+// Test struct with complex tuple fields (xlang mode)
+#[test]
+fn test_struct_with_complex_tuple_fields_xlang() {
+    run_struct_with_complex_tuple_fields(true);
+}
diff --git a/rust/tests/tests/test_tuple_compatible.rs 
b/rust/tests/tests/test_tuple_compatible.rs
new file mode 100644
index 000000000..79e3ed2eb
--- /dev/null
+++ b/rust/tests/tests/test_tuple_compatible.rs
@@ -0,0 +1,1068 @@
+// 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.
+
+//! Comprehensive tests for tuple serialization in compatible mode.
+//! These tests verify schema evolution capabilities including:
+//! - Tuple length mismatches (growing/shrinking)
+//! - Tuples with collections (Vec, HashMap, HashSet)
+//! - Nested tuples
+//! - Tuples with Option/Arc/Rc elements
+//! - Schema evolution scenarios
+
+use fory_core::fory::Fory;
+use fory_derive::ForyObject;
+use std::collections::{HashMap, HashSet};
+use std::rc::Rc;
+use std::sync::Arc;
+
+const PI_F64: f64 = std::f64::consts::PI;
+
+/// Test 1: Direct tuple size mismatch - bidirectional serialization
+#[test]
+fn test_tuple_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Test 1a: Long tuple serialized, short tuple deserialized
+    let long = (42i32, "hello".to_string(), PI_F64, true);
+    let bin = fory.serialize(&long).unwrap();
+    let short: (i32, String) = fory.deserialize(&bin).expect("deserialize long 
to short");
+    assert_eq!(short.0, 42);
+    assert_eq!(short.1, "hello");
+
+    // Test 1b: Short tuple serialized, long tuple deserialized
+    let short = (100i32, "world".to_string());
+    let bin = fory.serialize(&short).unwrap();
+    let long: (i32, String, f64, bool) = 
fory.deserialize(&bin).expect("deserialize short to long");
+    assert_eq!(long.0, 100);
+    assert_eq!(long.1, "world");
+    // Remaining fields should be default values
+    assert_eq!(long.2, 0.0);
+    assert!(!long.3);
+}
+
+/// Test 2: Tuples containing list/set/map elements
+#[test]
+fn test_tuple_with_collections_compatible() {
+    let fory = Fory::default().compatible(true);
+    // Tuple with Vec
+    let tuple_vec = (vec![1, 2, 3], vec!["a".to_string(), "b".to_string()]);
+    let bin = fory.serialize(&tuple_vec).unwrap();
+    let obj: (Vec<i32>, Vec<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_vec, obj);
+
+    // Tuple with HashSet
+    let mut set1 = HashSet::new();
+    set1.insert(1);
+    set1.insert(2);
+    let mut set2 = HashSet::new();
+    set2.insert("x".to_string());
+    let tuple_set = (set1.clone(), set2.clone());
+    let bin = fory.serialize(&tuple_set).unwrap();
+    let obj: (HashSet<i32>, HashSet<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_set, obj);
+
+    // Tuple with HashMap
+    let mut map1 = HashMap::new();
+    map1.insert("key1".to_string(), 100);
+    map1.insert("key2".to_string(), 200);
+    let tuple_map = (map1.clone(), 42i32);
+    let bin = fory.serialize(&tuple_map).unwrap();
+    let obj: (HashMap<String, i32>, i32) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_map, obj);
+
+    // Test adding/missing tuple elements with collections
+    // Long to short
+    let long = (vec![1, 2, 3], vec!["a".to_string()], vec![1.0, 2.0]);
+    let bin = fory.serialize(&long).unwrap();
+    let short: (Vec<i32>,) = fory.deserialize(&bin).expect("deserialize long 
to short");
+    assert_eq!(short.0, vec![1, 2, 3]);
+
+    // Short to long
+    let short = (vec![10, 20, 30],);
+    let bin = fory.serialize(&short).unwrap();
+    let long: (Vec<i32>, Vec<String>, Vec<f64>) =
+        fory.deserialize(&bin).expect("deserialize short to long");
+    assert_eq!(long.0, vec![10, 20, 30]);
+    assert_eq!(long.1, Vec::<String>::new()); // default
+    assert_eq!(long.2, Vec::<f64>::new()); // default
+}
+
+/// Test 2b: Tuple with collections - length mismatch
+#[test]
+fn test_tuple_collections_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize tuple with 3 collections
+    let tuple_long = (vec![1, 2, 3], vec!["a".to_string()], vec![1.0, 2.0]);
+    let bin = fory.serialize(&tuple_long).unwrap();
+
+    // Deserialize to tuple with 1 collection
+    let tuple_short: (Vec<i32>,) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_short.0, vec![1, 2, 3]);
+
+    // Serialize tuple with 1 collection
+    let tuple_short = (vec![10, 20, 30],);
+    let bin = fory.serialize(&tuple_short).unwrap();
+
+    // Deserialize to tuple with 3 collections
+    let tuple_long: (Vec<i32>, Vec<String>, Vec<f64>) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_long.0, vec![10, 20, 30]);
+    assert_eq!(tuple_long.1, Vec::<String>::new());
+    assert_eq!(tuple_long.2, Vec::<f64>::new());
+}
+
+/// Test 3: Nested tuples
+#[test]
+fn test_nested_tuples() {
+    let fory = Fory::default().compatible(true);
+
+    let obj = ((42i32, "hello".to_string()), (PI_F64, true));
+    let bin = fory.serialize(&obj).unwrap();
+    let deserialized: ((i32, String), (f64, bool)) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(obj, deserialized);
+}
+
+/// Test 3b: Nested tuple size mismatch
+#[test]
+fn test_nested_tuple_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Long to short
+    let long = ((42i32, "test".to_string(), PI_F64), (true, 100i32));
+    let bin = fory.serialize(&long).unwrap();
+    let short: ((i32, String), (bool,)) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(short.0 .0, 42);
+    assert_eq!(short.0 .1, "test");
+    assert!(short.1 .0);
+
+    // Short to long
+    let short = ((100i32, "hello".to_string()), (false,));
+    let bin = fory.serialize(&short).unwrap();
+    let long: ((i32, String, f64), (bool, i32)) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(long.0 .0, 100);
+    assert_eq!(long.0 .1, "hello");
+    assert_eq!(long.0 .2, 0.0); // default
+    assert!(!long.1 .0);
+    assert_eq!(long.1 .1, 0); // default
+}
+
+/// Test 3c: Deeply nested tuples with size mismatch
+#[test]
+fn test_deeply_nested_tuple_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize deeply nested tuple
+    let deep = (1i32, (2i32, (3i32, 4i32, 5i32)));
+    let bin = fory.serialize(&deep).unwrap();
+
+    // Deserialize to shallower structure
+    let shallow: (i32, (i32, (i32, i32))) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(shallow.0, 1);
+    assert_eq!(shallow.1 .0, 2);
+    assert_eq!(shallow.1 .1 .0, 3);
+    assert_eq!(shallow.1 .1 .1, 4);
+
+    // Reverse: shallow to deep
+    let shallow = (10i32, (20i32, (30i32,)));
+    let bin = fory.serialize(&shallow).unwrap();
+    let deep: (i32, (i32, (i32, i32, i32))) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(deep.0, 10);
+    assert_eq!(deep.1 .0, 20);
+    assert_eq!(deep.1 .1 .0, 30);
+    assert_eq!(deep.1 .1 .1, 0); // default
+    assert_eq!(deep.1 .1 .2, 0); // default
+}
+
+/// Test 4: Tuples with Option/Arc elements
+#[test]
+fn test_tuple_with_option_arc_compatible() {
+    let fory = Fory::default().compatible(true);
+
+    // Tuple with Options
+    let tuple_opt = (Some(42i32), None::<String>, Some(PI_F64));
+    let bin = fory.serialize(&tuple_opt).unwrap();
+    let obj: (Option<i32>, Option<String>, Option<f64>) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(tuple_opt, obj);
+
+    // Tuple with Arc
+    let tuple_arc = (Arc::new(42i32), Arc::new("hello".to_string()));
+    let bin = fory.serialize(&tuple_arc).unwrap();
+    let obj: (Arc<i32>, Arc<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*obj.0, 42);
+    assert_eq!(*obj.1, "hello");
+
+    // Tuple with Rc
+    let tuple_rc = (Rc::new(100i32), Rc::new("world".to_string()));
+    let bin = fory.serialize(&tuple_rc).unwrap();
+    let obj: (Rc<i32>, Rc<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*obj.0, 100);
+    assert_eq!(*obj.1, "world");
+
+    // Mixed: Option and Arc
+    let tuple_mixed = (Some(Arc::new(100i32)), None::<Arc<String>>);
+    let bin = fory.serialize(&tuple_mixed).unwrap();
+    let obj: (Option<Arc<i32>>, Option<Arc<String>>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*obj.0.unwrap(), 100);
+    assert!(obj.1.is_none());
+}
+
+/// Test 4b: Tuple with Option size mismatch
+#[test]
+fn test_tuple_option_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize longer tuple
+    let long = (
+        Some(42i32),
+        Some("hello".to_string()),
+        Some(PI_F64),
+        Some(true),
+    );
+    let bin = fory.serialize(&long).unwrap();
+
+    // Deserialize to shorter tuple
+    let short: (Option<i32>, Option<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(short.0, Some(42));
+    assert_eq!(short.1, Some("hello".to_string()));
+
+    // Serialize shorter tuple
+    let short = (Some(100i32), None::<String>);
+    let bin = fory.serialize(&short).unwrap();
+
+    // Deserialize to longer tuple
+    let long: (Option<i32>, Option<String>, Option<f64>, Option<bool>) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(long.0, Some(100));
+    assert_eq!(long.1, None);
+    assert_eq!(long.2, None); // default for Option is None
+    assert_eq!(long.3, None); // default for Option is None
+}
+
+/// Test 4c: Tuple with Arc size mismatch
+#[test]
+fn test_tuple_arc_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize longer tuple
+    let long = (Arc::new(1i32), Arc::new(2i32), Arc::new(3i32));
+    let bin = fory.serialize(&long).unwrap();
+
+    // Deserialize to shorter tuple
+    let short: (Arc<i32>, Arc<i32>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*short.0, 1);
+    assert_eq!(*short.1, 2);
+
+    // Serialize shorter tuple
+    let short = (Arc::new(10i32), Arc::new(20i32));
+    let bin = fory.serialize(&short).unwrap();
+
+    // Deserialize to longer tuple - Arc defaults are created via ForyDefault
+    let long: (Arc<i32>, Arc<i32>, Arc<i32>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(*long.0, 10);
+    assert_eq!(*long.1, 20);
+    assert_eq!(*long.2, 0); // default Arc<i32>
+}
+
+/// Test 5: Schema evolution from homogeneous to heterogeneous tuple
+#[test]
+fn test_tuple_homogeneous_to_heterogeneous() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize as homogeneous (all i32)
+    let homogeneous = (1i32, 2i32, 3i32);
+    let bin = fory.serialize(&homogeneous).unwrap();
+
+    // Deserialize as homogeneous - should work fine
+    let result: (i32, i32, i32) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(result, (1, 2, 3));
+
+    // Now test heterogeneous tuple
+    let heterogeneous = (10i32, "hello".to_string(), PI_F64);
+    let bin = fory.serialize(&heterogeneous).unwrap();
+
+    // This should work because compatible mode preserves type info
+    let result: (i32, String, f64) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(result.0, 10);
+    assert_eq!(result.1, "hello");
+    assert_eq!(result.2, PI_F64);
+}
+
+/// Test 6: Schema evolution with different element counts
+#[test]
+fn test_tuple_element_count_evolution() {
+    let fory = Fory::default().compatible(true);
+
+    // Test growing from 2 to 5 elements
+    let small = (42i32, "hello".to_string());
+    let bin = fory.serialize(&small).unwrap();
+    let large: (i32, String, f64, bool, i32) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(large.0, 42);
+    assert_eq!(large.1, "hello");
+    assert_eq!(large.2, 0.0); // default
+    assert!(!large.3); // default
+    assert_eq!(large.4, 0); // default
+
+    // Test shrinking from 5 to 2 elements
+    let large = (100i32, "world".to_string(), 2.71f64, true, 999i32);
+    let bin = fory.serialize(&large).unwrap();
+    let small: (i32, String) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(small.0, 100);
+    assert_eq!(small.1, "world");
+
+    // Test single element tuple evolution
+    let single = (123i32,);
+    let bin = fory.serialize(&single).unwrap();
+    let triple: (i32, i32, i32) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(triple.0, 123);
+    assert_eq!(triple.1, 0); // default
+    assert_eq!(triple.2, 0); // default
+
+    // Test triple to single
+    let triple = (1i32, 2i32, 3i32);
+    let bin = fory.serialize(&triple).unwrap();
+    let single: (i32,) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(single.0, 1);
+}
+
+/// Test 6b: Complex element count evolution
+#[test]
+fn test_tuple_element_count_evolution_complex() {
+    let fory = Fory::default().compatible(true);
+
+    // v1: simple 2-element tuple
+    let v1 = (42i32, "hello".to_string());
+    let bin = fory.serialize(&v1).unwrap();
+
+    // v2: evolved to 5-element tuple with collections
+    let v2: (i32, String, f64, bool, Vec<i32>) =
+        fory.deserialize(&bin).expect("deserialize v1 to v2");
+    assert_eq!(v2.0, 42);
+    assert_eq!(v2.1, "hello");
+    assert_eq!(v2.2, 0.0);
+    assert!(!v2.3);
+    assert_eq!(v2.4, Vec::<i32>::new());
+
+    // v2 to v1
+    let v2 = (100i32, "world".to_string(), PI_F64, true, vec![1, 2, 3]);
+    let bin = fory.serialize(&v2).unwrap();
+    let v1: (i32, String) = fory.deserialize(&bin).expect("deserialize v2 to 
v1");
+    assert_eq!(v1.0, 100);
+    assert_eq!(v1.1, "world");
+}
+
+/// Test 7: Edge case - empty tuple behavior
+#[test]
+fn test_empty_to_non_empty_tuple() {
+    let fory = Fory::default().compatible(true);
+
+    // Simulate deserializing to tuple when data is missing
+    // This is tested implicitly through struct field defaults
+    let single = (42i32,);
+    let bin = fory.serialize(&single).unwrap();
+    let result: (i32,) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(result.0, 42);
+}
+
+/// Test 8: Very large tuple with size mismatch
+#[test]
+fn test_large_tuple_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Serialize a large tuple (10 elements)
+    let large = (1i32, 2i32, 3i32, 4i32, 5i32, 6i32, 7i32, 8i32, 9i32, 10i32);
+    let bin = fory.serialize(&large).unwrap();
+
+    // Deserialize to small tuple (3 elements)
+    let small: (i32, i32, i32) = fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(small, (1, 2, 3));
+
+    // Serialize small tuple
+    let small = (100i32, 200i32, 300i32);
+    let bin = fory.serialize(&small).unwrap();
+
+    // Deserialize to large tuple
+    let large: (i32, i32, i32, i32, i32, i32, i32, i32, i32, i32) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(large.0, 100);
+    assert_eq!(large.1, 200);
+    assert_eq!(large.2, 300);
+    assert_eq!(large.3, 0);
+    assert_eq!(large.4, 0);
+    assert_eq!(large.5, 0);
+    assert_eq!(large.6, 0);
+    assert_eq!(large.7, 0);
+    assert_eq!(large.8, 0);
+    assert_eq!(large.9, 0);
+}
+
+/// Test 9: Mixed complex types with size mismatch
+#[test]
+fn test_mixed_complex_types_size_mismatch() {
+    let fory = Fory::default().compatible(true);
+
+    // Complex tuple with many different types
+    let complex = (
+        vec![1, 2, 3],
+        Some("hello".to_string()),
+        Arc::new(42i32),
+        (1i32, 2i32),
+    );
+    let bin = fory.serialize(&complex).unwrap();
+
+    // Deserialize to simpler tuple
+    let simple: (Vec<i32>, Option<String>) = 
fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(simple.0, vec![1, 2, 3]);
+    assert_eq!(simple.1, Some("hello".to_string()));
+
+    // Reverse direction
+    let simple = (vec![10, 20], None::<String>);
+    let bin = fory.serialize(&simple).unwrap();
+
+    let complex: (Vec<i32>, Option<String>, Arc<i32>, (i32, i32)) =
+        fory.deserialize(&bin).expect("deserialize");
+    assert_eq!(complex.0, vec![10, 20]);
+    assert_eq!(complex.1, None);
+    assert_eq!(*complex.2, 0); // default Arc<i32>
+    assert_eq!(complex.3, (0, 0)); // default tuple
+}
+
+/// Test compatible mode with tuples
+#[test]
+fn test_tuple_xlang_compatible_mode() {
+    let fory = Fory::default().compatible(true);
+    // Test basic tuple
+    let basic = (42i32, "hello".to_string(), vec![1, 2, 3]);
+    let bin = fory.serialize(&basic).unwrap();
+    let obj: (i32, String, Vec<i32>) = 
fory.deserialize(&bin).expect("deserialize basic");
+    assert_eq!(basic, obj);
+
+    // Test tuple size mismatch
+    let long = (1i32, "test".to_string(), PI_F64, true, vec![1, 2]);
+    let bin = fory.serialize(&long).unwrap();
+    let short: (i32, String) = fory.deserialize(&bin).expect("deserialize long 
to short");
+    assert_eq!(short.0, 1);
+    assert_eq!(short.1, "test");
+
+    // Test short to long
+    let short = (100i32, "world".to_string());
+    let bin = fory.serialize(&short).unwrap();
+    let long: (i32, String, f64, bool) = 
fory.deserialize(&bin).expect("deserialize short to long");
+    assert_eq!(long.0, 100);
+    assert_eq!(long.1, "world");
+    assert_eq!(long.2, 0.0);
+    assert!(!long.3);
+
+    // Test nested tuples with size mismatch
+    let nested = ((1i32, 2i32, 3i32), ("a".to_string(), "b".to_string()));
+    let bin = fory.serialize(&nested).unwrap();
+    let smaller: ((i32, i32), (String,)) = 
fory.deserialize(&bin).expect("deserialize nested");
+    assert_eq!(smaller.0 .0, 1);
+    assert_eq!(smaller.0 .1, 2);
+    assert_eq!(smaller.1 .0, "a");
+}
+
+// ============================================================================
+// Struct-based tests for tuple field compatibility
+// ============================================================================
+
+/// Helper: Test struct with missing tuple field
+fn run_struct_missing_tuple_field(xlang: bool) {
+    // V1: Struct with tuple field
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        name: String,
+        coordinates: (f64, f64),
+        metadata: (String, i32),
+    }
+
+    // V2: Struct with missing tuple field (coordinates removed)
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV2 {
+        id: i32,
+        name: String,
+        metadata: (String, i32),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(1).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(1).unwrap();
+
+    // Serialize V1 and deserialize as V2
+    let v1 = StructV1 {
+        id: 42,
+        name: "test".to_string(),
+        coordinates: (10.5, 20.3),
+        metadata: ("meta".to_string(), 100),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.name, "test");
+    assert_eq!(v2.metadata, ("meta".to_string(), 100));
+
+    // Serialize V2 and deserialize as V1 (coordinates should be default)
+    let v2 = StructV2 {
+        id: 99,
+        name: "test2".to_string(),
+        metadata: ("data".to_string(), 200),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.name, "test2");
+    assert_eq!(v1.coordinates, (0.0, 0.0)); // default
+    assert_eq!(v1.metadata, ("data".to_string(), 200));
+}
+
+/// Helper: Test struct with added tuple field
+fn run_struct_added_tuple_field(xlang: bool) {
+    // V1: Struct without extra tuple field
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        name: String,
+    }
+
+    // V2: Struct with added tuple field
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV2 {
+        id: i32,
+        name: String,
+        coordinates: (f64, f64),
+        tags: (String, String, i32),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(2).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(2).unwrap();
+
+    // Serialize V1 and deserialize as V2 (new tuple fields should be default)
+    let v1 = StructV1 {
+        id: 42,
+        name: "test".to_string(),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.name, "test");
+    assert_eq!(v2.coordinates, (0.0, 0.0)); // default
+    assert_eq!(v2.tags, (String::new(), String::new(), 0)); // default
+
+    // Serialize V2 and deserialize as V1
+    let v2 = StructV2 {
+        id: 99,
+        name: "test2".to_string(),
+        coordinates: (1.5, 2.5),
+        tags: ("tag1".to_string(), "tag2".to_string(), 123),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.name, "test2");
+}
+
+/// Helper: Test struct with tuple field element increase
+fn run_struct_tuple_element_increase(xlang: bool) {
+    // V1: Struct with 2-element tuple
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        coordinates: (f64, f64),
+    }
+
+    // V2: Struct with 4-element tuple (increased from 2)
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV2 {
+        id: i32,
+        coordinates: (f64, f64, f64, f64),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(3).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(3).unwrap();
+
+    // Serialize V1 and deserialize as V2 (extra elements should be default)
+    let v1 = StructV1 {
+        id: 42,
+        coordinates: (10.5, 20.3),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.coordinates.0, 10.5);
+    assert_eq!(v2.coordinates.1, 20.3);
+    assert_eq!(v2.coordinates.2, 0.0); // default
+    assert_eq!(v2.coordinates.3, 0.0); // default
+
+    // Serialize V2 and deserialize as V1 (extra elements should be truncated)
+    let v2 = StructV2 {
+        id: 99,
+        coordinates: (1.0, 2.0, 3.0, 4.0),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.coordinates, (1.0, 2.0));
+}
+
+/// Helper: Test struct with tuple field element decrease
+fn run_struct_tuple_element_decrease(xlang: bool) {
+    // V1: Struct with 5-element tuple
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        data: (i32, String, f64, bool, Vec<i32>),
+    }
+
+    // V2: Struct with 2-element tuple (decreased from 5)
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV2 {
+        id: i32,
+        data: (i32, String),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(4).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(4).unwrap();
+
+    // Serialize V1 and deserialize as V2 (extra elements should be dropped)
+    let v1 = StructV1 {
+        id: 42,
+        data: (100, "hello".to_string(), PI_F64, true, vec![1, 2, 3]),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.data.0, 100);
+    assert_eq!(v2.data.1, "hello");
+
+    // Serialize V2 and deserialize as V1 (missing elements should be default)
+    let v2 = StructV2 {
+        id: 99,
+        data: (200, "world".to_string()),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.data.0, 200);
+    assert_eq!(v1.data.1, "world");
+    assert_eq!(v1.data.2, 0.0); // default
+    assert!(!v1.data.3); // default
+    assert_eq!(v1.data.4, Vec::<i32>::new()); // default
+}
+
+/// Helper: Test struct with complex nested tuple evolution
+fn run_struct_nested_tuple_evolution(xlang: bool) {
+    // V1: Struct with simple nested tuple
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        nested: ((i32, String), (f64, bool)),
+    }
+
+    // V2: Struct with evolved nested tuple (more elements)
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[allow(clippy::type_complexity)]
+    struct StructV2 {
+        id: i32,
+        nested: ((i32, String, Vec<i32>), (f64, bool, Option<String>)),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(5).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(5).unwrap();
+
+    // Serialize V1 and deserialize as V2
+    let v1 = StructV1 {
+        id: 42,
+        nested: ((100, "test".to_string()), (PI_F64, true)),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.nested.0 .0, 100);
+    assert_eq!(v2.nested.0 .1, "test");
+    assert_eq!(v2.nested.0 .2, Vec::<i32>::new()); // default
+    assert_eq!(v2.nested.1 .0, PI_F64);
+    assert!(v2.nested.1 .1);
+    assert_eq!(v2.nested.1 .2, None); // default
+
+    // Serialize V2 and deserialize as V1
+    let v2 = StructV2 {
+        id: 99,
+        nested: (
+            (200, "world".to_string(), vec![1, 2, 3]),
+            (2.71, false, Some("extra".to_string())),
+        ),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.nested.0 .0, 200);
+    assert_eq!(v1.nested.0 .1, "world");
+    assert_eq!(v1.nested.1 .0, 2.71);
+    assert!(!v1.nested.1 .1);
+}
+
+/// Helper: Test struct with multiple tuple fields evolution
+fn run_struct_multiple_tuple_fields_evolution(xlang: bool) {
+    // V1: Struct with two tuple fields
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV1 {
+        id: i32,
+        coords: (f64, f64),
+        tags: (String, i32),
+    }
+
+    // V2: Struct with modified tuple fields (coords expanded, tags reduced)
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct StructV2 {
+        id: i32,
+        coords: (f64, f64, f64), // 3D coordinates (expanded from 2D)
+        tags: (String,),         // reduced to single tag (from 2 elements)
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<StructV1>(6).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<StructV2>(6).unwrap();
+
+    // Serialize V1 and deserialize as V2
+    let v1 = StructV1 {
+        id: 42,
+        coords: (1.5, 2.5),
+        tags: ("tag1".to_string(), 100),
+    };
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: StructV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.coords.0, 1.5);
+    assert_eq!(v2.coords.1, 2.5);
+    assert_eq!(v2.coords.2, 0.0); // default
+    assert_eq!(v2.tags.0, "tag1");
+
+    // Serialize V2 and deserialize as V1
+    let v2 = StructV2 {
+        id: 99,
+        coords: (10.0, 20.0, 30.0),
+        tags: ("newtag".to_string(),),
+    };
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: StructV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.coords, (10.0, 20.0));
+    assert_eq!(v1.tags.0, "newtag");
+    assert_eq!(v1.tags.1, 0); // default
+}
+
+// Test functions (non-xlang mode)
+#[test]
+fn test_struct_missing_tuple_field() {
+    run_struct_missing_tuple_field(false);
+}
+
+#[test]
+fn test_struct_added_tuple_field() {
+    run_struct_added_tuple_field(false);
+}
+
+#[test]
+fn test_struct_tuple_element_increase() {
+    run_struct_tuple_element_increase(false);
+}
+
+#[test]
+fn test_struct_tuple_element_decrease() {
+    run_struct_tuple_element_decrease(false);
+}
+
+#[test]
+fn test_struct_nested_tuple_evolution() {
+    run_struct_nested_tuple_evolution(false);
+}
+
+#[test]
+fn test_struct_multiple_tuple_fields_evolution() {
+    run_struct_multiple_tuple_fields_evolution(false);
+}
+
+// Test functions (xlang mode)
+#[test]
+fn test_struct_missing_tuple_field_xlang() {
+    run_struct_missing_tuple_field(true);
+}
+
+#[test]
+fn test_struct_added_tuple_field_xlang() {
+    run_struct_added_tuple_field(true);
+}
+
+#[test]
+fn test_struct_tuple_element_increase_xlang() {
+    run_struct_tuple_element_increase(true);
+}
+
+#[test]
+fn test_struct_tuple_element_decrease_xlang() {
+    run_struct_tuple_element_decrease(true);
+}
+
+#[test]
+fn test_struct_nested_tuple_evolution_xlang() {
+    run_struct_nested_tuple_evolution(true);
+}
+
+#[test]
+fn test_struct_multiple_tuple_fields_evolution_xlang() {
+    run_struct_multiple_tuple_fields_evolution(true);
+}
+
+// ============================================================================
+// Complex scenario tests (ignored - advanced edge cases)
+// ============================================================================
+
+/// Test complex scenario combining field-level and tuple-element evolution 
(non-xlang mode)
+///
+/// This test is ignored because it tests a very complex scenario that 
combines:
+/// - Adding new struct fields (status, attributes)
+/// - Removing struct fields (implicitly tested in reverse direction)
+/// - Expanding tuple elements (position: 2->3, metadata nested tuples)
+/// - Reducing tuple elements (category: 2->1)
+/// - Unchanged tuple fields (tags)
+/// - Mix of simple, nested, and collection-based tuples
+///
+/// This represents a realistic schema evolution scenario where multiple 
changes
+/// happen simultaneously across a complex data structure.
+#[test]
+fn test_struct_complex_evolution_scenario() {
+    run_struct_complex_evolution_scenario(false);
+}
+
+/// Test complex scenario combining field-level and tuple-element evolution 
(xlang mode)
+///
+/// Same as test_struct_complex_evolution_scenario but with xlang=true.
+/// This tests whether the cross-language serialization protocol can handle
+/// complex schema evolution scenarios.
+#[test]
+fn test_struct_complex_evolution_scenario_xlang() {
+    run_struct_complex_evolution_scenario(true);
+}
+
+/// Helper: Test very complex scenario combining field-level and tuple-element 
evolution
+/// This test combines:
+/// - Adding/removing struct fields
+/// - Changing tuple element counts in existing fields
+/// - Multiple tuple fields evolving simultaneously
+/// - Mix of simple, nested, and collection-based tuples
+fn run_struct_complex_evolution_scenario(xlang: bool) {
+    // V1: Original schema with multiple tuple fields
+    #[derive(ForyObject, Debug, PartialEq)]
+    struct DataRecordV1 {
+        id: i32,
+        name: String,
+        // Simple 2D coordinates
+        position: (f64, f64),
+        // 2-element tuple with string and int
+        category: (String, i32),
+        // Nested tuple
+        metadata: ((String, i32), (bool, f64)),
+        // Tuple with collection
+        tags: (Vec<String>, Vec<i32>),
+    }
+
+    // V2: Evolved schema with complex changes
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[allow(clippy::type_complexity)]
+    struct DataRecordV2 {
+        id: i32,
+        name: String,
+        // position expanded to 3D coordinates (2 -> 3 elements)
+        position: (f64, f64, f64),
+        // category reduced to single element (2 -> 1 elements)
+        category: (String,),
+        // metadata nested tuple expanded (both inner tuples gain elements)
+        metadata: ((String, i32, Vec<String>), (bool, f64, Option<i32>)),
+        // tags remains same
+        tags: (Vec<String>, Vec<i32>),
+        // NEW FIELD: status tuple added
+        status: (bool, String, i32),
+        // NEW FIELD: nested tuple with collections
+        attributes: ((Vec<String>, HashMap<String, i32>), (Option<bool>,)),
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().xlang(xlang).compatible(true);
+    fory1.register::<DataRecordV1>(100).unwrap();
+
+    let mut fory2 = Fory::default().xlang(xlang).compatible(true);
+    fory2.register::<DataRecordV2>(100).unwrap();
+
+    // Test V1 -> V2: Old schema to new schema
+    let v1 = DataRecordV1 {
+        id: 42,
+        name: "record1".to_string(),
+        position: (10.5, 20.3),
+        category: ("TypeA".to_string(), 100),
+        metadata: (("meta_key".to_string(), 999), (true, PI_F64)),
+        tags: (vec!["tag1".to_string(), "tag2".to_string()], vec![1, 2, 3]),
+    };
+
+    let bytes = fory1.serialize(&v1).unwrap();
+    let v2: DataRecordV2 = fory2.deserialize(&bytes).expect("deserialize V1 to 
V2");
+
+    // Verify existing fields
+    assert_eq!(v2.id, 42);
+    assert_eq!(v2.name, "record1");
+
+    // position: (10.5, 20.3) -> (10.5, 20.3, 0.0)
+    assert_eq!(v2.position.0, 10.5);
+    assert_eq!(v2.position.1, 20.3);
+    assert_eq!(v2.position.2, 0.0); // default
+
+    // category: ("TypeA", 100) -> ("TypeA",)
+    assert_eq!(v2.category.0, "TypeA");
+
+    // metadata expanded with defaults
+    assert_eq!(v2.metadata.0 .0, "meta_key");
+    assert_eq!(v2.metadata.0 .1, 999);
+    assert_eq!(v2.metadata.0 .2, Vec::<String>::new()); // default
+    assert!(v2.metadata.1 .0);
+    assert_eq!(v2.metadata.1 .1, PI_F64);
+    assert_eq!(v2.metadata.1 .2, None); // default
+
+    // tags unchanged
+    assert_eq!(v2.tags.0, vec!["tag1".to_string(), "tag2".to_string()]);
+    assert_eq!(v2.tags.1, vec![1, 2, 3]);
+
+    // New fields should have defaults
+    assert_eq!(v2.status, (false, String::new(), 0)); // default tuple
+    assert_eq!(v2.attributes.0 .0, Vec::<String>::new());
+    assert_eq!(v2.attributes.0 .1, HashMap::<String, i32>::new());
+    assert_eq!(v2.attributes.1 .0, None);
+
+    // Test V2 -> V1: New schema to old schema
+    let mut attrs_map = HashMap::new();
+    attrs_map.insert("key1".to_string(), 42);
+    attrs_map.insert("key2".to_string(), 99);
+
+    let v2 = DataRecordV2 {
+        id: 99,
+        name: "record2".to_string(),
+        position: (100.0, 200.0, 300.0),
+        category: ("TypeB".to_string(),),
+        metadata: (
+            ("new_meta".to_string(), 777, vec!["m1".to_string()]),
+            (false, 2.71, Some(555)),
+        ),
+        tags: (vec!["new_tag".to_string()], vec![10, 20]),
+        status: (true, "active".to_string(), 123),
+        attributes: ((vec!["attr1".to_string()], attrs_map), (Some(true),)),
+    };
+
+    let bytes = fory2.serialize(&v2).unwrap();
+    let v1: DataRecordV1 = fory1.deserialize(&bytes).expect("deserialize V2 to 
V1");
+
+    // Verify existing fields
+    assert_eq!(v1.id, 99);
+    assert_eq!(v1.name, "record2");
+
+    // position: (100.0, 200.0, 300.0) -> (100.0, 200.0)
+    assert_eq!(v1.position, (100.0, 200.0));
+
+    // category: ("TypeB",) -> ("TypeB", 0)
+    assert_eq!(v1.category.0, "TypeB");
+    assert_eq!(v1.category.1, 0); // default
+
+    // metadata truncated
+    assert_eq!(v1.metadata.0 .0, "new_meta");
+    assert_eq!(v1.metadata.0 .1, 777);
+    assert!(!v1.metadata.1 .0);
+    assert_eq!(v1.metadata.1 .1, 2.71);
+
+    // tags unchanged
+    assert_eq!(v1.tags.0, vec!["new_tag".to_string()]);
+    assert_eq!(v1.tags.1, vec![10, 20]);
+}
+
+type AttributeTuple = ((Option<bool>,), (Vec<String>, HashMap<String, i32>));
+
+#[test]
+fn test_tuple_alias() {
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[allow(clippy::type_complexity)]
+    struct DataRecordV1 {
+        attrs: ((Option<bool>,), (Vec<String>,)),
+    }
+
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[allow(clippy::type_complexity)]
+    struct DataRecordV2 {
+        attrs: AttributeTuple,
+    }
+
+    // Use separate Fory instances with the same type ID
+    let mut fory1 = Fory::default().compatible(true);
+    fory1.register::<DataRecordV1>(100).unwrap();
+
+    let mut fory2 = Fory::default().compatible(true);
+    fory2.register::<DataRecordV2>(100).unwrap();
+
+    // Test record1 serialized by fory1, deserialized by fory2
+    // Type alias can't be recognized, so attrs will be treated as missing 
field
+    let record1 = DataRecordV1 {
+        attrs: ((Some(true),), (vec!["test".to_string()],)),
+    };
+    let bytes1 = fory1.serialize(&record1).unwrap();
+    let deserialized1: DataRecordV2 = fory2.deserialize(&bytes1).unwrap();
+    assert_eq!(
+        deserialized1.attrs,
+        ((Some(true),), (vec!["test".to_string()], HashMap::new()))
+    );
+
+    // Test record2 serialized by fory2, deserialized by fory1
+    let mut map = HashMap::new();
+    map.insert("key".to_string(), 42);
+    let record2 = DataRecordV2 {
+        attrs: ((Some(false),), (vec!["example".to_string()], map)),
+    };
+    let bytes2 = fory2.serialize(&record2).unwrap();
+    let deserialized2: DataRecordV1 = fory1.deserialize(&bytes2).unwrap();
+    assert_eq!(
+        deserialized2.attrs,
+        ((Some(false),), (vec!["example".to_string()],))
+    );
+}


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

Reply via email to