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]