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 da5da4b21 feat(c++): support unsigned type for cpp (#3022)
da5da4b21 is described below

commit da5da4b21d6515bc90b8ea44c2b3eac589cef2fd
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Dec 9 12:31:28 2025 +0800

    feat(c++): support unsigned type for cpp (#3022)
    
    ## Why?
    
    C++ natively supports unsigned integer types (`uint8_t`, `uint16_t`,
    `uint32_t`, `uint64_t`) which are widely used in systems programming.
    This PR adds serialization support for these types in native mode
    (`xlang=false`), following the same pattern as the Rust implementation.
    
    ## What does this PR do?
    
    1. **Adds unsigned TypeIds** to `cpp/fory/type/type.h`:
    - `U8 = 64`, `U16 = 65`, `U32 = 66`, `U64 = 67` for basic unsigned types
    - `U16_ARRAY = 73`, `U32_ARRAY = 74`, `U64_ARRAY = 75` for unsigned
    arrays
    
    2. **Implements unsigned type serializers** in
    `cpp/fory/serialization/unsigned_serializer.h`:
       - Basic types: `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`
       - Fixed-size arrays: `std::array<uintN_t, N>`
       - Vectors: `std::vector<uintN_t>`
    
    3. **Updates struct serializer** to recognize unsigned types as
    primitive types, enabling efficient serialization of structs with
    unsigned fields.
    
    4. **Adds comprehensive tests** covering:
       - Primitive unsigned type roundtrips
       - Unsigned vector and array roundtrips
       - Structs with unsigned fields
       - Boundary value tests
       - TypeId verification
    
    **Note**: Unsigned types are only supported in native mode
    (`xlang=false`). This follows the xlang specification which does not
    include unsigned types for cross-language compatibility.
    
    ## Related issues
    
    #2906
    
    ## Does this PR introduce any user-facing change?
    
    Yes - users can now serialize/deserialize unsigned integer types in C++
    when using native mode.
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    N/A - This is a new feature addition, not a performance optimization.
---
 AGENTS.md                                          |   2 +
 cpp/fory/serialization/BUILD                       |  11 +
 cpp/fory/serialization/basic_serializer.h          | 287 ------
 cpp/fory/serialization/serializer.h                |   1 +
 cpp/fory/serialization/struct_serializer.h         |   8 +-
 cpp/fory/serialization/unsigned_serializer.h       | 999 +++++++++++++++++++++
 cpp/fory/serialization/unsigned_serializer_test.cc | 275 ++++++
 cpp/fory/type/type.h                               |  18 +-
 8 files changed, 1311 insertions(+), 290 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 639321e8f..7edd8dcd0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -54,6 +54,8 @@ mvn -T16 test -Dtest=org.apache.fory.TestClass#testMethod
 - Fory c++ use c++ 17, you must not use features from higher version of C++.
 - Whnen you updated the code, use `clang-format` to update the code
 - When invoking a method that returns `Result`, always use `FORY_TRY` unless 
in a control flow context.
+- Wrap error checks with `FORY_PREDICT_FALSE` for branch prediction 
optimization.
+- Continue on error for trivial errors; only return early for critical errors 
like buffer overflow.
 - private methods should be put last in class def, before private fields.
 
 ```bash
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index 74e26fadc..534613da0 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -26,6 +26,7 @@ cc_library(
         "tuple_serializer.h",
         "type_info.h",
         "type_resolver.h",
+        "unsigned_serializer.h",
     ],
     strip_include_prefix = "/cpp",
     deps = [
@@ -108,6 +109,16 @@ cc_test(
     ],
 )
 
+cc_test(
+    name = "unsigned_serializer_test",
+    srcs = ["unsigned_serializer_test.cc"],
+    deps = [
+        ":fory_serialization",
+        "@googletest//:gtest",
+        "@googletest//:gtest_main",
+    ],
+)
+
 cc_binary(
     name = "xlang_test_main",
     srcs = ["xlang_test_main.cc"],
diff --git a/cpp/fory/serialization/basic_serializer.h 
b/cpp/fory/serialization/basic_serializer.h
index 28dea8c09..68d8bb84f 100644
--- a/cpp/fory/serialization/basic_serializer.h
+++ b/cpp/fory/serialization/basic_serializer.h
@@ -534,293 +534,6 @@ template <> struct Serializer<double> {
   }
 };
 
-// ============================================================================
-// Unsigned Integer Type Serializers
-// ============================================================================
-
-/// uint8_t serializer
-template <> struct Serializer<uint8_t> {
-  static constexpr TypeId type_id = TypeId::INT8;
-
-  static inline void write_type_info(WriteContext &ctx) {
-    ctx.write_varuint32(static_cast<uint32_t>(type_id));
-  }
-
-  static inline void read_type_info(ReadContext &ctx) {
-    uint32_t actual = ctx.read_varuint32(ctx.error());
-    if (FORY_PREDICT_FALSE(ctx.has_error())) {
-      return;
-    }
-    if (actual != static_cast<uint32_t>(type_id)) {
-      ctx.set_error(
-          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
-    }
-  }
-
-  static inline void write(uint8_t value, WriteContext &ctx, bool write_ref,
-                           bool write_type, bool has_generics = false) {
-    write_not_null_ref_flag(ctx, write_ref);
-    if (write_type) {
-      ctx.write_varuint32(static_cast<uint32_t>(type_id));
-    }
-    write_data(value, ctx);
-  }
-
-  static inline void write_data(uint8_t value, WriteContext &ctx) {
-    ctx.write_uint8(value);
-  }
-
-  static inline void write_data_generic(uint8_t value, WriteContext &ctx,
-                                        bool has_generics) {
-    write_data(value, ctx);
-  }
-
-  static inline uint8_t read(ReadContext &ctx, bool read_ref, bool read_type) {
-    bool has_value = consume_ref_flag(ctx, read_ref);
-    if (ctx.has_error() || !has_value) {
-      return 0;
-    }
-    if (read_type) {
-      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
-      if (FORY_PREDICT_FALSE(ctx.has_error())) {
-        return 0;
-      }
-      if (type_id_read != static_cast<uint32_t>(type_id)) {
-        ctx.set_error(
-            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
-        return 0;
-      }
-    }
-    return ctx.read_uint8(ctx.error());
-  }
-
-  static inline uint8_t read_data(ReadContext &ctx) {
-    return ctx.read_uint8(ctx.error());
-  }
-
-  static inline uint8_t read_data_generic(ReadContext &ctx, bool has_generics) 
{
-    return read_data(ctx);
-  }
-
-  static inline uint8_t read_with_type_info(ReadContext &ctx, bool read_ref,
-                                            const TypeInfo &type_info) {
-    return read(ctx, read_ref, false);
-  }
-};
-
-/// uint16_t serializer
-template <> struct Serializer<uint16_t> {
-  static constexpr TypeId type_id = TypeId::INT16;
-
-  static inline void write_type_info(WriteContext &ctx) {
-    ctx.write_varuint32(static_cast<uint32_t>(type_id));
-  }
-
-  static inline void read_type_info(ReadContext &ctx) {
-    uint32_t actual = ctx.read_varuint32(ctx.error());
-    if (FORY_PREDICT_FALSE(ctx.has_error())) {
-      return;
-    }
-    if (actual != static_cast<uint32_t>(type_id)) {
-      ctx.set_error(
-          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
-    }
-  }
-
-  static inline void write(uint16_t value, WriteContext &ctx, bool write_ref,
-                           bool write_type, bool has_generics = false) {
-    write_not_null_ref_flag(ctx, write_ref);
-    if (write_type) {
-      ctx.write_varuint32(static_cast<uint32_t>(type_id));
-    }
-    write_data(value, ctx);
-  }
-
-  static inline void write_data(uint16_t value, WriteContext &ctx) {
-    ctx.write_bytes(&value, sizeof(uint16_t));
-  }
-
-  static inline void write_data_generic(uint16_t value, WriteContext &ctx,
-                                        bool has_generics) {
-    write_data(value, ctx);
-  }
-
-  static inline uint16_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
-    bool has_value = consume_ref_flag(ctx, read_ref);
-    if (ctx.has_error() || !has_value) {
-      return 0;
-    }
-    if (read_type) {
-      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
-      if (FORY_PREDICT_FALSE(ctx.has_error())) {
-        return 0;
-      }
-      if (type_id_read != static_cast<uint32_t>(type_id)) {
-        ctx.set_error(
-            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
-        return 0;
-      }
-    }
-    return ctx.read_uint16(ctx.error());
-  }
-
-  static inline uint16_t read_data(ReadContext &ctx) {
-    return ctx.read_uint16(ctx.error());
-  }
-
-  static inline uint16_t read_data_generic(ReadContext &ctx,
-                                           bool has_generics) {
-    return read_data(ctx);
-  }
-
-  static inline uint16_t read_with_type_info(ReadContext &ctx, bool read_ref,
-                                             const TypeInfo &type_info) {
-    return read(ctx, read_ref, false);
-  }
-};
-
-/// uint32_t serializer
-template <> struct Serializer<uint32_t> {
-  static constexpr TypeId type_id = TypeId::INT32;
-
-  static inline void write_type_info(WriteContext &ctx) {
-    ctx.write_varuint32(static_cast<uint32_t>(type_id));
-  }
-
-  static inline void read_type_info(ReadContext &ctx) {
-    uint32_t actual = ctx.read_varuint32(ctx.error());
-    if (FORY_PREDICT_FALSE(ctx.has_error())) {
-      return;
-    }
-    if (actual != static_cast<uint32_t>(type_id)) {
-      ctx.set_error(
-          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
-    }
-  }
-
-  static inline void write(uint32_t value, WriteContext &ctx, bool write_ref,
-                           bool write_type, bool has_generics = false) {
-    write_not_null_ref_flag(ctx, write_ref);
-    if (write_type) {
-      ctx.write_varuint32(static_cast<uint32_t>(type_id));
-    }
-    write_data(value, ctx);
-  }
-
-  static inline void write_data(uint32_t value, WriteContext &ctx) {
-    ctx.write_bytes(&value, sizeof(uint32_t));
-  }
-
-  static inline void write_data_generic(uint32_t value, WriteContext &ctx,
-                                        bool has_generics) {
-    write_data(value, ctx);
-  }
-
-  static inline uint32_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
-    bool has_value = consume_ref_flag(ctx, read_ref);
-    if (ctx.has_error() || !has_value) {
-      return 0;
-    }
-    if (read_type) {
-      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
-      if (FORY_PREDICT_FALSE(ctx.has_error())) {
-        return 0;
-      }
-      if (type_id_read != static_cast<uint32_t>(type_id)) {
-        ctx.set_error(
-            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
-        return 0;
-      }
-    }
-    return ctx.read_uint32(ctx.error());
-  }
-
-  static inline uint32_t read_data(ReadContext &ctx) {
-    return ctx.read_uint32(ctx.error());
-  }
-
-  static inline uint32_t read_data_generic(ReadContext &ctx,
-                                           bool has_generics) {
-    return read_data(ctx);
-  }
-
-  static inline uint32_t read_with_type_info(ReadContext &ctx, bool read_ref,
-                                             const TypeInfo &type_info) {
-    return read(ctx, read_ref, false);
-  }
-};
-
-/// uint64_t serializer
-template <> struct Serializer<uint64_t> {
-  static constexpr TypeId type_id = TypeId::INT64;
-
-  static inline void write_type_info(WriteContext &ctx) {
-    ctx.write_varuint32(static_cast<uint32_t>(type_id));
-  }
-
-  static inline void read_type_info(ReadContext &ctx) {
-    uint32_t actual = ctx.read_varuint32(ctx.error());
-    if (FORY_PREDICT_FALSE(ctx.has_error())) {
-      return;
-    }
-    if (actual != static_cast<uint32_t>(type_id)) {
-      ctx.set_error(
-          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
-    }
-  }
-
-  static inline void write(uint64_t value, WriteContext &ctx, bool write_ref,
-                           bool write_type, bool has_generics = false) {
-    write_not_null_ref_flag(ctx, write_ref);
-    if (write_type) {
-      ctx.write_varuint32(static_cast<uint32_t>(type_id));
-    }
-    write_data(value, ctx);
-  }
-
-  static inline void write_data(uint64_t value, WriteContext &ctx) {
-    ctx.write_bytes(&value, sizeof(uint64_t));
-  }
-
-  static inline void write_data_generic(uint64_t value, WriteContext &ctx,
-                                        bool has_generics) {
-    write_data(value, ctx);
-  }
-
-  static inline uint64_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
-    bool has_value = consume_ref_flag(ctx, read_ref);
-    if (ctx.has_error() || !has_value) {
-      return 0;
-    }
-    if (read_type) {
-      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
-      if (FORY_PREDICT_FALSE(ctx.has_error())) {
-        return 0;
-      }
-      if (type_id_read != static_cast<uint32_t>(type_id)) {
-        ctx.set_error(
-            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
-        return 0;
-      }
-    }
-    return ctx.read_uint64(ctx.error());
-  }
-
-  static inline uint64_t read_data(ReadContext &ctx) {
-    return ctx.read_uint64(ctx.error());
-  }
-
-  static inline uint64_t read_data_generic(ReadContext &ctx,
-                                           bool has_generics) {
-    return read_data(ctx);
-  }
-
-  static inline uint64_t read_with_type_info(ReadContext &ctx, bool read_ref,
-                                             const TypeInfo &type_info) {
-    return read(ctx, read_ref, false);
-  }
-};
-
 // ============================================================================
 // String Serializer
 // ============================================================================
diff --git a/cpp/fory/serialization/serializer.h 
b/cpp/fory/serialization/serializer.h
index b260e1521..1cac3b37b 100644
--- a/cpp/fory/serialization/serializer.h
+++ b/cpp/fory/serialization/serializer.h
@@ -242,3 +242,4 @@ template <typename T, typename Enable> struct Serializer {
 // Include all specialized serializers
 #include "fory/serialization/basic_serializer.h"
 #include "fory/serialization/enum_serializer.h"
+#include "fory/serialization/unsigned_serializer.h"
diff --git a/cpp/fory/serialization/struct_serializer.h 
b/cpp/fory/serialization/struct_serializer.h
index 772fbddb9..5cd76ab28 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -150,7 +150,8 @@ namespace detail {
 
 /// Helper to check if a TypeId represents a primitive type.
 /// Per xlang spec, primitive types are: bool, int8-64, var_int32/64,
-/// sli_int64, float16/32/64. All other types (string, list, set, map, struct,
+/// sli_int64, float16/32/64. For native mode (xlang=false), also includes
+/// unsigned types: u8-64. All other types (string, list, set, map, struct,
 /// enum, etc.) are non-primitive and require ref flags.
 inline constexpr bool is_primitive_type_id(TypeId type_id) {
   return type_id == TypeId::BOOL || type_id == TypeId::INT8 ||
@@ -158,7 +159,10 @@ inline constexpr bool is_primitive_type_id(TypeId type_id) 
{
          type_id == TypeId::VAR_INT32 || type_id == TypeId::INT64 ||
          type_id == TypeId::VAR_INT64 || type_id == TypeId::SLI_INT64 ||
          type_id == TypeId::FLOAT16 || type_id == TypeId::FLOAT32 ||
-         type_id == TypeId::FLOAT64;
+         type_id == TypeId::FLOAT64 ||
+         // Unsigned types for native mode (xlang=false)
+         type_id == TypeId::U8 || type_id == TypeId::U16 ||
+         type_id == TypeId::U32 || type_id == TypeId::U64;
 }
 
 /// Write a primitive value to buffer at given offset WITHOUT updating
diff --git a/cpp/fory/serialization/unsigned_serializer.h 
b/cpp/fory/serialization/unsigned_serializer.h
new file mode 100644
index 000000000..b1295865f
--- /dev/null
+++ b/cpp/fory/serialization/unsigned_serializer.h
@@ -0,0 +1,999 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include "fory/serialization/context.h"
+#include "fory/serialization/serializer_traits.h"
+#include "fory/type/type.h"
+#include "fory/util/error.h"
+#include <array>
+#include <cstdint>
+#include <vector>
+
+namespace fory {
+namespace serialization {
+
+// ============================================================================
+// Unsigned Integer Type Serializers (xlang=false mode only)
+// These serializers use distinct TypeIds for unsigned types.
+// Unsigned types are NOT supported in xlang mode per the xlang spec.
+// ============================================================================
+
+/// uint8_t serializer (native mode only)
+template <> struct Serializer<uint8_t> {
+  static constexpr TypeId type_id = TypeId::U8;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(actual != static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(uint8_t value, WriteContext &ctx, bool write_ref,
+                           bool write_type, bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data(value, ctx);
+  }
+
+  static inline void write_data(uint8_t value, WriteContext &ctx) {
+    ctx.write_uint8(value);
+  }
+
+  static inline void write_data_generic(uint8_t value, WriteContext &ctx,
+                                        bool has_generics) {
+    write_data(value, ctx);
+  }
+
+  static inline uint8_t read(ReadContext &ctx, bool read_ref, bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return 0;
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return ctx.read_uint8(ctx.error());
+  }
+
+  static inline uint8_t read_data(ReadContext &ctx) {
+    return ctx.read_uint8(ctx.error());
+  }
+
+  static inline uint8_t read_data_generic(ReadContext &ctx, bool has_generics) 
{
+    return read_data(ctx);
+  }
+
+  static inline uint8_t read_with_type_info(ReadContext &ctx, bool read_ref,
+                                            const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// uint16_t serializer (native mode only)
+template <> struct Serializer<uint16_t> {
+  static constexpr TypeId type_id = TypeId::U16;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(actual != static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(uint16_t value, WriteContext &ctx, bool write_ref,
+                           bool write_type, bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data(value, ctx);
+  }
+
+  static inline void write_data(uint16_t value, WriteContext &ctx) {
+    ctx.write_bytes(&value, sizeof(uint16_t));
+  }
+
+  static inline void write_data_generic(uint16_t value, WriteContext &ctx,
+                                        bool has_generics) {
+    write_data(value, ctx);
+  }
+
+  static inline uint16_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return 0;
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return ctx.read_uint16(ctx.error());
+  }
+
+  static inline uint16_t read_data(ReadContext &ctx) {
+    return ctx.read_uint16(ctx.error());
+  }
+
+  static inline uint16_t read_data_generic(ReadContext &ctx,
+                                           bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline uint16_t read_with_type_info(ReadContext &ctx, bool read_ref,
+                                             const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// uint32_t serializer (native mode only)
+template <> struct Serializer<uint32_t> {
+  static constexpr TypeId type_id = TypeId::U32;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(actual != static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(uint32_t value, WriteContext &ctx, bool write_ref,
+                           bool write_type, bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data(value, ctx);
+  }
+
+  static inline void write_data(uint32_t value, WriteContext &ctx) {
+    ctx.write_bytes(&value, sizeof(uint32_t));
+  }
+
+  static inline void write_data_generic(uint32_t value, WriteContext &ctx,
+                                        bool has_generics) {
+    write_data(value, ctx);
+  }
+
+  static inline uint32_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return 0;
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return ctx.read_uint32(ctx.error());
+  }
+
+  static inline uint32_t read_data(ReadContext &ctx) {
+    return ctx.read_uint32(ctx.error());
+  }
+
+  static inline uint32_t read_data_generic(ReadContext &ctx,
+                                           bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline uint32_t read_with_type_info(ReadContext &ctx, bool read_ref,
+                                             const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// uint64_t serializer (native mode only)
+template <> struct Serializer<uint64_t> {
+  static constexpr TypeId type_id = TypeId::U64;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(actual != static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(uint64_t value, WriteContext &ctx, bool write_ref,
+                           bool write_type, bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data(value, ctx);
+  }
+
+  static inline void write_data(uint64_t value, WriteContext &ctx) {
+    ctx.write_bytes(&value, sizeof(uint64_t));
+  }
+
+  static inline void write_data_generic(uint64_t value, WriteContext &ctx,
+                                        bool has_generics) {
+    write_data(value, ctx);
+  }
+
+  static inline uint64_t read(ReadContext &ctx, bool read_ref, bool read_type) 
{
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return 0;
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return ctx.read_uint64(ctx.error());
+  }
+
+  static inline uint64_t read_data(ReadContext &ctx) {
+    return ctx.read_uint64(ctx.error());
+  }
+
+  static inline uint64_t read_data_generic(ReadContext &ctx,
+                                           bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline uint64_t read_with_type_info(ReadContext &ctx, bool read_ref,
+                                             const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+// ============================================================================
+// Unsigned Integer Array Serializers (std::array, native mode only)
+// ============================================================================
+
+/// Serializer for std::array<uint8_t, N> (native mode only)
+/// Note: uint8_t arrays use INT8_ARRAY (BINARY) type which is compatible
+template <size_t N> struct Serializer<std::array<uint8_t, N>> {
+  static constexpr TypeId type_id = TypeId::INT8_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::array<uint8_t, N> &arr, WriteContext 
&ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(arr, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::array<uint8_t, N> &arr,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    constexpr size_t max_size = 8 + N * sizeof(uint8_t);
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index += buffer.PutVarUint32(writer_index, 
static_cast<uint32_t>(N));
+    if constexpr (N > 0) {
+      buffer.UnsafePut(writer_index, arr.data(), N * sizeof(uint8_t));
+    }
+    buffer.WriterIndex(writer_index + N * sizeof(uint8_t));
+  }
+
+  static inline void write_data_generic(const std::array<uint8_t, N> &arr,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(arr, ctx);
+  }
+
+  static inline std::array<uint8_t, N> read(ReadContext &ctx, bool read_ref,
+                                            bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::array<uint8_t, N>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::array<uint8_t, N> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length != N || length * sizeof(uint8_t) >
+                                              ctx.buffer().remaining_size())) {
+      ctx.set_error(Error::invalid_data("Array size mismatch: expected " +
+                                        std::to_string(N) + " but got " +
+                                        std::to_string(length)));
+      return std::array<uint8_t, N>();
+    }
+    std::array<uint8_t, N> arr;
+    if constexpr (N > 0) {
+      ctx.read_bytes(arr.data(), N * sizeof(uint8_t), ctx.error());
+    }
+    return arr;
+  }
+
+  static inline std::array<uint8_t, N>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::array<uint16_t, N> (native mode only)
+template <size_t N> struct Serializer<std::array<uint16_t, N>> {
+  static constexpr TypeId type_id = TypeId::U16_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::array<uint16_t, N> &arr,
+                           WriteContext &ctx, bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(arr, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::array<uint16_t, N> &arr,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    constexpr size_t max_size = 8 + N * sizeof(uint16_t);
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index += buffer.PutVarUint32(writer_index, 
static_cast<uint32_t>(N));
+    if constexpr (N > 0) {
+      buffer.UnsafePut(writer_index, arr.data(), N * sizeof(uint16_t));
+    }
+    buffer.WriterIndex(writer_index + N * sizeof(uint16_t));
+  }
+
+  static inline void write_data_generic(const std::array<uint16_t, N> &arr,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(arr, ctx);
+  }
+
+  static inline std::array<uint16_t, N> read(ReadContext &ctx, bool read_ref,
+                                             bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::array<uint16_t, N>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::array<uint16_t, N> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length != N || length * sizeof(uint16_t) >
+                                              ctx.buffer().remaining_size())) {
+      ctx.set_error(Error::invalid_data("Array size mismatch: expected " +
+                                        std::to_string(N) + " but got " +
+                                        std::to_string(length)));
+      return std::array<uint16_t, N>();
+    }
+    std::array<uint16_t, N> arr;
+    if constexpr (N > 0) {
+      ctx.read_bytes(arr.data(), N * sizeof(uint16_t), ctx.error());
+    }
+    return arr;
+  }
+
+  static inline std::array<uint16_t, N>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::array<uint32_t, N> (native mode only)
+template <size_t N> struct Serializer<std::array<uint32_t, N>> {
+  static constexpr TypeId type_id = TypeId::U32_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::array<uint32_t, N> &arr,
+                           WriteContext &ctx, bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(arr, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::array<uint32_t, N> &arr,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    constexpr size_t max_size = 8 + N * sizeof(uint32_t);
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index += buffer.PutVarUint32(writer_index, 
static_cast<uint32_t>(N));
+    if constexpr (N > 0) {
+      buffer.UnsafePut(writer_index, arr.data(), N * sizeof(uint32_t));
+    }
+    buffer.WriterIndex(writer_index + N * sizeof(uint32_t));
+  }
+
+  static inline void write_data_generic(const std::array<uint32_t, N> &arr,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(arr, ctx);
+  }
+
+  static inline std::array<uint32_t, N> read(ReadContext &ctx, bool read_ref,
+                                             bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::array<uint32_t, N>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::array<uint32_t, N> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length != N || length * sizeof(uint32_t) >
+                                              ctx.buffer().remaining_size())) {
+      ctx.set_error(Error::invalid_data("Array size mismatch: expected " +
+                                        std::to_string(N) + " but got " +
+                                        std::to_string(length)));
+      return std::array<uint32_t, N>();
+    }
+    std::array<uint32_t, N> arr;
+    if constexpr (N > 0) {
+      ctx.read_bytes(arr.data(), N * sizeof(uint32_t), ctx.error());
+    }
+    return arr;
+  }
+
+  static inline std::array<uint32_t, N>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::array<uint64_t, N> (native mode only)
+template <size_t N> struct Serializer<std::array<uint64_t, N>> {
+  static constexpr TypeId type_id = TypeId::U64_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::array<uint64_t, N> &arr,
+                           WriteContext &ctx, bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(arr, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::array<uint64_t, N> &arr,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    constexpr size_t max_size = 8 + N * sizeof(uint64_t);
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index += buffer.PutVarUint32(writer_index, 
static_cast<uint32_t>(N));
+    if constexpr (N > 0) {
+      buffer.UnsafePut(writer_index, arr.data(), N * sizeof(uint64_t));
+    }
+    buffer.WriterIndex(writer_index + N * sizeof(uint64_t));
+  }
+
+  static inline void write_data_generic(const std::array<uint64_t, N> &arr,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(arr, ctx);
+  }
+
+  static inline std::array<uint64_t, N> read(ReadContext &ctx, bool read_ref,
+                                             bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::array<uint64_t, N>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::array<uint64_t, N> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length != N || length * sizeof(uint64_t) >
+                                              ctx.buffer().remaining_size())) {
+      ctx.set_error(Error::invalid_data("Array size mismatch: expected " +
+                                        std::to_string(N) + " but got " +
+                                        std::to_string(length)));
+      return std::array<uint64_t, N>();
+    }
+    std::array<uint64_t, N> arr;
+    if constexpr (N > 0) {
+      ctx.read_bytes(arr.data(), N * sizeof(uint64_t), ctx.error());
+    }
+    return arr;
+  }
+
+  static inline std::array<uint64_t, N>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+// ============================================================================
+// std::vector serializers for unsigned types (native mode only)
+// ============================================================================
+
+/// Serializer for std::vector<uint8_t> (binary data)
+/// Note: uint8_t vectors use BINARY type
+template <> struct Serializer<std::vector<uint8_t>> {
+  static constexpr TypeId type_id = TypeId::BINARY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::vector<uint8_t> &vec, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      ctx.write_varuint32(static_cast<uint32_t>(type_id));
+    }
+    write_data_generic(vec, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::vector<uint8_t> &vec,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    size_t max_size = 8 + vec.size();
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index +=
+        buffer.PutVarUint32(writer_index, static_cast<uint32_t>(vec.size()));
+    if (!vec.empty()) {
+      buffer.UnsafePut(writer_index, vec.data(), vec.size());
+    }
+    buffer.WriterIndex(writer_index + static_cast<uint32_t>(vec.size()));
+  }
+
+  static inline void write_data_generic(const std::vector<uint8_t> &vec,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(vec, ctx);
+  }
+
+  static inline std::vector<uint8_t> read(ReadContext &ctx, bool read_ref,
+                                          bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::vector<uint8_t>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint8_t> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length > ctx.buffer().remaining_size())) {
+      ctx.set_error(
+          Error::invalid_data("Invalid length: " + std::to_string(length)));
+      return std::vector<uint8_t>();
+    }
+    std::vector<uint8_t> vec(length);
+    if (length > 0) {
+      ctx.read_bytes(vec.data(), length, ctx.error());
+    }
+    return vec;
+  }
+
+  static inline std::vector<uint8_t> read_data_generic(ReadContext &ctx,
+                                                       bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint8_t>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::vector<uint16_t> (native mode only)
+template <> struct Serializer<std::vector<uint16_t>> {
+  static constexpr TypeId type_id = TypeId::U16_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::vector<uint16_t> &vec, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(vec, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::vector<uint16_t> &vec,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    size_t data_size = vec.size() * sizeof(uint16_t);
+    size_t max_size = 8 + data_size;
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index +=
+        buffer.PutVarUint32(writer_index, static_cast<uint32_t>(vec.size()));
+    if (!vec.empty()) {
+      buffer.UnsafePut(writer_index, vec.data(), data_size);
+    }
+    buffer.WriterIndex(writer_index + static_cast<uint32_t>(data_size));
+  }
+
+  static inline void write_data_generic(const std::vector<uint16_t> &vec,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(vec, ctx);
+  }
+
+  static inline std::vector<uint16_t> read(ReadContext &ctx, bool read_ref,
+                                           bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::vector<uint16_t>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint16_t> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length * sizeof(uint16_t) >
+                           ctx.buffer().remaining_size())) {
+      ctx.set_error(
+          Error::invalid_data("Invalid length: " + std::to_string(length)));
+      return std::vector<uint16_t>();
+    }
+    std::vector<uint16_t> vec(length);
+    if (length > 0) {
+      ctx.read_bytes(vec.data(), length * sizeof(uint16_t), ctx.error());
+    }
+    return vec;
+  }
+
+  static inline std::vector<uint16_t> read_data_generic(ReadContext &ctx,
+                                                        bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint16_t>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::vector<uint32_t> (native mode only)
+template <> struct Serializer<std::vector<uint32_t>> {
+  static constexpr TypeId type_id = TypeId::U32_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::vector<uint32_t> &vec, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(vec, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::vector<uint32_t> &vec,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    size_t data_size = vec.size() * sizeof(uint32_t);
+    size_t max_size = 8 + data_size;
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index +=
+        buffer.PutVarUint32(writer_index, static_cast<uint32_t>(vec.size()));
+    if (!vec.empty()) {
+      buffer.UnsafePut(writer_index, vec.data(), data_size);
+    }
+    buffer.WriterIndex(writer_index + static_cast<uint32_t>(data_size));
+  }
+
+  static inline void write_data_generic(const std::vector<uint32_t> &vec,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(vec, ctx);
+  }
+
+  static inline std::vector<uint32_t> read(ReadContext &ctx, bool read_ref,
+                                           bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::vector<uint32_t>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint32_t> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length * sizeof(uint32_t) >
+                           ctx.buffer().remaining_size())) {
+      ctx.set_error(
+          Error::invalid_data("Invalid length: " + std::to_string(length)));
+      return std::vector<uint32_t>();
+    }
+    std::vector<uint32_t> vec(length);
+    if (length > 0) {
+      ctx.read_bytes(vec.data(), length * sizeof(uint32_t), ctx.error());
+    }
+    return vec;
+  }
+
+  static inline std::vector<uint32_t> read_data_generic(ReadContext &ctx,
+                                                        bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint32_t>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+/// Serializer for std::vector<uint64_t> (native mode only)
+template <> struct Serializer<std::vector<uint64_t>> {
+  static constexpr TypeId type_id = TypeId::U64_ARRAY;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(
+            !type_id_matches(actual, static_cast<uint32_t>(type_id)))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline void write(const std::vector<uint64_t> &vec, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    write_not_null_ref_flag(ctx, write_ref);
+    if (write_type) {
+      write_type_info(ctx);
+    }
+    write_data_generic(vec, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::vector<uint64_t> &vec,
+                                WriteContext &ctx) {
+    Buffer &buffer = ctx.buffer();
+    size_t data_size = vec.size() * sizeof(uint64_t);
+    size_t max_size = 8 + data_size;
+    buffer.Grow(static_cast<uint32_t>(max_size));
+    uint32_t writer_index = buffer.writer_index();
+    writer_index +=
+        buffer.PutVarUint32(writer_index, static_cast<uint32_t>(vec.size()));
+    if (!vec.empty()) {
+      buffer.UnsafePut(writer_index, vec.data(), data_size);
+    }
+    buffer.WriterIndex(writer_index + static_cast<uint32_t>(data_size));
+  }
+
+  static inline void write_data_generic(const std::vector<uint64_t> &vec,
+                                        WriteContext &ctx, bool has_generics) {
+    write_data(vec, ctx);
+  }
+
+  static inline std::vector<uint64_t> read(ReadContext &ctx, bool read_ref,
+                                           bool read_type) {
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (!has_value) {
+      return std::vector<uint64_t>();
+    }
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(type_id_read != static_cast<uint32_t>(type_id))) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+      }
+    }
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint64_t> read_data(ReadContext &ctx) {
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(length * sizeof(uint64_t) >
+                           ctx.buffer().remaining_size())) {
+      ctx.set_error(
+          Error::invalid_data("Invalid length: " + std::to_string(length)));
+      return std::vector<uint64_t>();
+    }
+    std::vector<uint64_t> vec(length);
+    if (length > 0) {
+      ctx.read_bytes(vec.data(), length * sizeof(uint64_t), ctx.error());
+    }
+    return vec;
+  }
+
+  static inline std::vector<uint64_t> read_data_generic(ReadContext &ctx,
+                                                        bool has_generics) {
+    return read_data(ctx);
+  }
+
+  static inline std::vector<uint64_t>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+};
+
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/serialization/unsigned_serializer_test.cc 
b/cpp/fory/serialization/unsigned_serializer_test.cc
new file mode 100644
index 000000000..8397a37d3
--- /dev/null
+++ b/cpp/fory/serialization/unsigned_serializer_test.cc
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "fory/serialization/fory.h"
+#include "gtest/gtest.h"
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <vector>
+
+namespace fory {
+namespace serialization {
+namespace test {
+
+// ============================================================================
+// Test Struct with Unsigned Fields
+// ============================================================================
+
+struct UnsignedStruct {
+  uint8_t u8_val;
+  uint16_t u16_val;
+  uint32_t u32_val;
+  uint64_t u64_val;
+
+  bool operator==(const UnsignedStruct &other) const {
+    return u8_val == other.u8_val && u16_val == other.u16_val &&
+           u32_val == other.u32_val && u64_val == other.u64_val;
+  }
+};
+
+FORY_STRUCT(UnsignedStruct, u8_val, u16_val, u32_val, u64_val);
+
+struct UnsignedArrayStruct {
+  std::vector<uint8_t> u8_vec;
+  std::vector<uint16_t> u16_vec;
+  std::vector<uint32_t> u32_vec;
+  std::vector<uint64_t> u64_vec;
+
+  bool operator==(const UnsignedArrayStruct &other) const {
+    return u8_vec == other.u8_vec && u16_vec == other.u16_vec &&
+           u32_vec == other.u32_vec && u64_vec == other.u64_vec;
+  }
+};
+
+FORY_STRUCT(UnsignedArrayStruct, u8_vec, u16_vec, u32_vec, u64_vec);
+
+// ============================================================================
+// Test Helper for Native Mode (xlang=false)
+// Unsigned types are only supported in native mode (xlang=false)
+// ============================================================================
+
+template <typename T> void test_roundtrip_native(const T &original) {
+  // Test with xlang=false (native mode) - the only supported mode for unsigned
+  // types
+  auto fory = Fory::builder().xlang(false).track_ref(false).build();
+
+  // Serialize
+  auto serialize_result = fory.serialize(original);
+  ASSERT_TRUE(serialize_result.ok())
+      << "Serialization failed: " << serialize_result.error().to_string();
+
+  std::vector<uint8_t> bytes = std::move(serialize_result).value();
+  ASSERT_GT(bytes.size(), 0) << "Serialized bytes should not be empty";
+
+  // Deserialize
+  auto deserialize_result = fory.deserialize<T>(bytes.data(), bytes.size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << "Deserialization failed: " << deserialize_result.error().to_string();
+
+  T deserialized = std::move(deserialize_result).value();
+
+  // Compare
+  EXPECT_EQ(original, deserialized);
+}
+
+// ============================================================================
+// Unsigned Primitive Type Tests (Native Mode Only)
+// ============================================================================
+
+TEST(UnsignedSerializerTest, Uint8NativeRoundtrip) {
+  test_roundtrip_native<uint8_t>(0);
+  test_roundtrip_native<uint8_t>(127);
+  test_roundtrip_native<uint8_t>(255);
+  test_roundtrip_native<uint8_t>(42);
+}
+
+TEST(UnsignedSerializerTest, Uint16NativeRoundtrip) {
+  test_roundtrip_native<uint16_t>(0);
+  test_roundtrip_native<uint16_t>(32767);
+  test_roundtrip_native<uint16_t>(65535);
+  test_roundtrip_native<uint16_t>(12345);
+}
+
+TEST(UnsignedSerializerTest, Uint32NativeRoundtrip) {
+  test_roundtrip_native<uint32_t>(0);
+  test_roundtrip_native<uint32_t>(2147483647);
+  test_roundtrip_native<uint32_t>(4294967295U);
+  test_roundtrip_native<uint32_t>(123456789);
+}
+
+TEST(UnsignedSerializerTest, Uint64NativeRoundtrip) {
+  test_roundtrip_native<uint64_t>(0);
+  test_roundtrip_native<uint64_t>(9223372036854775807ULL);
+  test_roundtrip_native<uint64_t>(18446744073709551615ULL);
+  test_roundtrip_native<uint64_t>(123456789012345ULL);
+}
+
+// ============================================================================
+// Unsigned Vector Tests (Native Mode Only)
+// ============================================================================
+
+TEST(UnsignedSerializerTest, VectorUint8NativeRoundtrip) {
+  test_roundtrip_native(std::vector<uint8_t>{});
+  test_roundtrip_native(std::vector<uint8_t>{0, 127, 255});
+  test_roundtrip_native(std::vector<uint8_t>{1, 2, 3, 4, 5});
+}
+
+TEST(UnsignedSerializerTest, VectorUint16NativeRoundtrip) {
+  test_roundtrip_native(std::vector<uint16_t>{});
+  test_roundtrip_native(std::vector<uint16_t>{0, 32767, 65535});
+  test_roundtrip_native(std::vector<uint16_t>{1000, 2000, 3000});
+}
+
+TEST(UnsignedSerializerTest, VectorUint32NativeRoundtrip) {
+  test_roundtrip_native(std::vector<uint32_t>{});
+  test_roundtrip_native(std::vector<uint32_t>{0, 2147483647, 4294967295U});
+  test_roundtrip_native(std::vector<uint32_t>{100000, 200000, 300000});
+}
+
+TEST(UnsignedSerializerTest, VectorUint64NativeRoundtrip) {
+  test_roundtrip_native(std::vector<uint64_t>{});
+  test_roundtrip_native(std::vector<uint64_t>{0, 9223372036854775807ULL,
+                                              18446744073709551615ULL});
+  test_roundtrip_native(
+      std::vector<uint64_t>{1000000000000ULL, 2000000000000ULL});
+}
+
+// ============================================================================
+// Unsigned Array Tests (Native Mode Only)
+// ============================================================================
+
+TEST(UnsignedSerializerTest, ArrayUint8NativeRoundtrip) {
+  test_roundtrip_native(std::array<uint8_t, 3>{0, 127, 255});
+  test_roundtrip_native(std::array<uint8_t, 5>{1, 2, 3, 4, 5});
+}
+
+TEST(UnsignedSerializerTest, ArrayUint16NativeRoundtrip) {
+  test_roundtrip_native(std::array<uint16_t, 3>{0, 32767, 65535});
+  test_roundtrip_native(std::array<uint16_t, 3>{1000, 2000, 3000});
+}
+
+TEST(UnsignedSerializerTest, ArrayUint32NativeRoundtrip) {
+  test_roundtrip_native(std::array<uint32_t, 3>{0, 2147483647, 4294967295U});
+  test_roundtrip_native(std::array<uint32_t, 3>{100000, 200000, 300000});
+}
+
+TEST(UnsignedSerializerTest, ArrayUint64NativeRoundtrip) {
+  test_roundtrip_native(std::array<uint64_t, 3>{0, 9223372036854775807ULL,
+                                                18446744073709551615ULL});
+  test_roundtrip_native(
+      std::array<uint64_t, 2>{1000000000000ULL, 2000000000000ULL});
+}
+
+// ============================================================================
+// Struct with Unsigned Fields Tests (Native Mode Only)
+// ============================================================================
+
+TEST(UnsignedSerializerTest, UnsignedStructNativeRoundtrip) {
+  auto fory = Fory::builder().xlang(false).track_ref(false).build();
+  fory.register_struct<UnsignedStruct>(1);
+
+  UnsignedStruct original{42, 1234, 567890, 9876543210ULL};
+
+  auto serialize_result = fory.serialize(original);
+  ASSERT_TRUE(serialize_result.ok())
+      << "Serialization failed: " << serialize_result.error().to_string();
+
+  std::vector<uint8_t> bytes = std::move(serialize_result).value();
+
+  auto deserialize_result =
+      fory.deserialize<UnsignedStruct>(bytes.data(), bytes.size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << "Deserialization failed: " << deserialize_result.error().to_string();
+
+  EXPECT_EQ(original, deserialize_result.value());
+}
+
+TEST(UnsignedSerializerTest, UnsignedArrayStructNativeRoundtrip) {
+  auto fory = Fory::builder().xlang(false).track_ref(false).build();
+  fory.register_struct<UnsignedArrayStruct>(1);
+
+  UnsignedArrayStruct original{
+      {1, 2, 3}, {100, 200}, {10000, 20000}, {1000000}};
+
+  auto serialize_result = fory.serialize(original);
+  ASSERT_TRUE(serialize_result.ok())
+      << "Serialization failed: " << serialize_result.error().to_string();
+
+  std::vector<uint8_t> bytes = std::move(serialize_result).value();
+
+  auto deserialize_result =
+      fory.deserialize<UnsignedArrayStruct>(bytes.data(), bytes.size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << "Deserialization failed: " << deserialize_result.error().to_string();
+
+  EXPECT_EQ(original, deserialize_result.value());
+}
+
+// ============================================================================
+// Edge Cases - Boundary Values
+// ============================================================================
+
+TEST(UnsignedSerializerTest, BoundaryValues) {
+  // Test min/max values for each unsigned type
+  test_roundtrip_native<uint8_t>(std::numeric_limits<uint8_t>::min());
+  test_roundtrip_native<uint8_t>(std::numeric_limits<uint8_t>::max());
+
+  test_roundtrip_native<uint16_t>(std::numeric_limits<uint16_t>::min());
+  test_roundtrip_native<uint16_t>(std::numeric_limits<uint16_t>::max());
+
+  test_roundtrip_native<uint32_t>(std::numeric_limits<uint32_t>::min());
+  test_roundtrip_native<uint32_t>(std::numeric_limits<uint32_t>::max());
+
+  test_roundtrip_native<uint64_t>(std::numeric_limits<uint64_t>::min());
+  test_roundtrip_native<uint64_t>(std::numeric_limits<uint64_t>::max());
+}
+
+// ============================================================================
+// Type ID Verification Tests
+// ============================================================================
+
+TEST(UnsignedSerializerTest, UnsignedTypeIdsAreDistinct) {
+  // Verify that unsigned types use distinct TypeIds (U8, U16, U32, U64)
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<uint8_t>::type_id),
+            static_cast<uint32_t>(TypeId::U8));
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<uint16_t>::type_id),
+            static_cast<uint32_t>(TypeId::U16));
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<uint32_t>::type_id),
+            static_cast<uint32_t>(TypeId::U32));
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<uint64_t>::type_id),
+            static_cast<uint32_t>(TypeId::U64));
+}
+
+TEST(UnsignedSerializerTest, UnsignedArrayTypeIdsAreDistinct) {
+  // Verify that unsigned array types use distinct TypeIds
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<std::vector<uint16_t>>::type_id),
+            static_cast<uint32_t>(TypeId::U16_ARRAY));
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<std::vector<uint32_t>>::type_id),
+            static_cast<uint32_t>(TypeId::U32_ARRAY));
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<std::vector<uint64_t>>::type_id),
+            static_cast<uint32_t>(TypeId::U64_ARRAY));
+  // uint8_t vector uses BINARY type
+  EXPECT_EQ(static_cast<uint32_t>(Serializer<std::vector<uint8_t>>::type_id),
+            static_cast<uint32_t>(TypeId::BINARY));
+}
+
+} // namespace test
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/type/type.h b/cpp/fory/type/type.h
index 3075b649e..d6221fbbb 100644
--- a/cpp/fory/type/type.h
+++ b/cpp/fory/type/type.h
@@ -103,9 +103,25 @@ enum class TypeId : int32_t {
   FLOAT32_ARRAY = 36,
   // one-dimensional float64 array.
   FLOAT64_ARRAY = 37,
+  // Unsigned integer types (native mode only, not supported in xlang mode)
+  // an 8-bit unsigned integer.
+  U8 = 64,
+  // a 16-bit unsigned integer.
+  U16 = 65,
+  // a 32-bit unsigned integer.
+  U32 = 66,
+  // a 64-bit unsigned integer.
+  U64 = 67,
+  // Unsigned integer array types (native mode only)
+  // one-dimensional uint16 array.
+  U16_ARRAY = 73,
+  // one-dimensional uint32 array.
+  U32_ARRAY = 74,
+  // one-dimensional uint64 array.
+  U64_ARRAY = 75,
   // Bound value for range checks (types with id >= BOUND are not internal
   // types).
-  BOUND = 64
+  BOUND = 78
 };
 
 inline bool IsUserType(int32_t type_id) {


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

Reply via email to