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 f5646ab24 chore(csharp): add more csharp and swift tests (#3597)
f5646ab24 is described below

commit f5646ab2446413333c674ad622ef8cca28c2127a
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Apr 21 14:03:30 2026 +0800

    chore(csharp): add more csharp and swift tests (#3597)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 csharp/src/Fory/TimeSerializers.cs                 |   7 +-
 csharp/tests/Fory.Tests/ByteBufferTests.cs         | 252 ++++++++++++++++
 csharp/tests/Fory.Tests/ForyRuntimeTests.cs        | 233 ++++++++++++++
 csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs    | 335 +++++++++++++++++++++
 csharp/tests/Fory.Tests/StringSerializerTests.cs   | 127 ++++++++
 swift/Sources/Fory/AnySerializer.swift             |  32 +-
 swift/Sources/Fory/TypeResolver.swift              |  50 ++-
 swift/Tests/ForyTests/AnyTests.swift               |  46 +++
 swift/Tests/ForyTests/ByteBufferTests.swift        | 230 ++++++++++++++
 .../ForyTests/CollectionSerializerTests.swift      | 313 +++++++++++++++++++
 swift/Tests/ForyTests/CompatibilityTests.swift     | 294 ++++++++++++++++++
 swift/Tests/ForyTests/ForySwiftTests.swift         | 138 +++++++++
 swift/Tests/ForyTests/StringSerializerTests.swift  | 147 +++++++++
 swift/Tests/ForyTests/UnsignedTests.swift          | 188 ++++++++++++
 14 files changed, 2369 insertions(+), 23 deletions(-)

diff --git a/csharp/src/Fory/TimeSerializers.cs 
b/csharp/src/Fory/TimeSerializers.cs
index 24c6825c1..61b216315 100644
--- a/csharp/src/Fory/TimeSerializers.cs
+++ b/csharp/src/Fory/TimeSerializers.cs
@@ -60,15 +60,16 @@ internal static class TimeCodec
     {
         long seconds = value.Ticks / TimeSpan.TicksPerSecond;
         int nanos = checked((int)((value.Ticks % TimeSpan.TicksPerSecond) * 
100));
-        context.Writer.WriteInt64(seconds);
+        context.Writer.WriteVarInt64(seconds);
         context.Writer.WriteInt32(nanos);
     }
 
     public static TimeSpan ReadDuration(ReadContext context)
     {
-        long seconds = context.Reader.ReadInt64();
+        long seconds = context.Reader.ReadVarInt64();
         int nanos = context.Reader.ReadInt32();
-        return TimeSpan.FromSeconds(seconds) + TimeSpan.FromTicks(nanos / 100);
+        long ticks = checked(seconds * TimeSpan.TicksPerSecond);
+        return TimeSpan.FromTicks(checked(ticks + (nanos / 100)));
     }
 
     private static (long Seconds, uint Nanos) ToTimestampParts(DateTimeOffset 
value)
diff --git a/csharp/tests/Fory.Tests/ByteBufferTests.cs 
b/csharp/tests/Fory.Tests/ByteBufferTests.cs
new file mode 100644
index 000000000..9da9705ac
--- /dev/null
+++ b/csharp/tests/Fory.Tests/ByteBufferTests.cs
@@ -0,0 +1,252 @@
+// 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.
+
+using Apache.Fory;
+
+namespace Apache.Fory.Tests;
+
+public sealed class ByteBufferTests
+{
+    public static TheoryData<uint, int> VarUInt32Cases => new()
+    {
+        { 0u, 1 },
+        { 0x7Fu, 1 },
+        { 0x80u, 2 },
+        { 0x3FFFu, 2 },
+        { 0x4000u, 3 },
+        { 0x1F_FFFFu, 3 },
+        { 0x20_0000u, 4 },
+        { 0x0FFF_FFFFu, 4 },
+        { 0x1000_0000u, 5 },
+        { uint.MaxValue, 5 },
+    };
+
+    public static TheoryData<ulong, int> VarUInt64Cases => new()
+    {
+        { 0UL, 1 },
+        { 0x7FUL, 1 },
+        { 0x80UL, 2 },
+        { (1UL << 14) - 1, 2 },
+        { 1UL << 14, 3 },
+        { (1UL << 21) - 1, 3 },
+        { 1UL << 21, 4 },
+        { (1UL << 28) - 1, 4 },
+        { 1UL << 28, 5 },
+        { (1UL << 35) - 1, 5 },
+        { 1UL << 35, 6 },
+        { (1UL << 42) - 1, 6 },
+        { 1UL << 42, 7 },
+        { (1UL << 49) - 1, 7 },
+        { 1UL << 49, 8 },
+        { (1UL << 56) - 1, 8 },
+        { 1UL << 56, 9 },
+        { ulong.MaxValue, 9 },
+    };
+
+    public static TheoryData<int> VarInt32Cases => new()
+    {
+        0,
+        -1,
+        1,
+        -63,
+        63,
+        int.MinValue,
+        int.MaxValue,
+    };
+
+    public static TheoryData<long> VarInt64Cases => new()
+    {
+        0L,
+        -1L,
+        1L,
+        -63L,
+        63L,
+        int.MinValue,
+        int.MaxValue,
+        long.MinValue,
+        long.MaxValue,
+    };
+
+    [Fact]
+    public void PrimitiveReadWriteRoundTrip()
+    {
+        ByteWriter writer = new(1);
+        writer.WriteUInt8(0xAB);
+        writer.WriteInt8(-7);
+        writer.WriteUInt16(0xCAFE);
+        writer.WriteInt16(-12_345);
+        writer.WriteUInt32(0x89ABCDEF);
+        writer.WriteInt32(-123_456_789);
+        writer.WriteUInt64(0xFEDCBA9876543210UL);
+        writer.WriteInt64(-1_234_567_890_123_456_789L);
+        writer.WriteFloat32(123.5f);
+        writer.WriteFloat64(-9876.25);
+        writer.WriteBytes([0x01, 0x02, 0x03, 0xFF]);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(0xAB, reader.ReadUInt8());
+        Assert.Equal(-7, reader.ReadInt8());
+        Assert.Equal(0xCAFE, reader.ReadUInt16());
+        Assert.Equal(-12_345, reader.ReadInt16());
+        Assert.Equal(0x89ABCDEFu, reader.ReadUInt32());
+        Assert.Equal(-123_456_789, reader.ReadInt32());
+        Assert.Equal(0xFEDCBA9876543210UL, reader.ReadUInt64());
+        Assert.Equal(-1_234_567_890_123_456_789L, reader.ReadInt64());
+        Assert.Equal(
+            BitConverter.SingleToInt32Bits(123.5f),
+            BitConverter.SingleToInt32Bits(reader.ReadFloat32()));
+        Assert.Equal(
+            BitConverter.DoubleToInt64Bits(-9876.25),
+            BitConverter.DoubleToInt64Bits(reader.ReadFloat64()));
+        Assert.Equal(new byte[] { 0x01, 0x02, 0x03, 0xFF }, 
reader.ReadBytes(4));
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Theory]
+    [MemberData(nameof(VarUInt32Cases))]
+    public void VarUInt32RoundTripAndSize(uint value, int expectedBytes)
+    {
+        ByteWriter writer = new();
+        writer.WriteVarUInt32(value);
+        Assert.Equal(expectedBytes, writer.Count);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadVarUInt32());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Theory]
+    [MemberData(nameof(VarUInt64Cases))]
+    public void VarUInt64RoundTripAndSize(ulong value, int expectedBytes)
+    {
+        ByteWriter writer = new();
+        writer.WriteVarUInt64(value);
+        Assert.Equal(expectedBytes, writer.Count);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadVarUInt64());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Theory]
+    [MemberData(nameof(VarInt32Cases))]
+    public void VarInt32RoundTrip(int value)
+    {
+        ByteWriter writer = new();
+        writer.WriteVarInt32(value);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadVarInt32());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Theory]
+    [MemberData(nameof(VarInt64Cases))]
+    public void VarInt64RoundTrip(long value)
+    {
+        ByteWriter writer = new();
+        writer.WriteVarInt64(value);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadVarInt64());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Fact]
+    public void VarUInt36SmallBoundariesAndOverflow()
+    {
+        ByteWriter writer = new();
+        foreach (ulong value in new[] { 0UL, 31UL, 32UL, 1UL << 35, (1UL << 
36) - 1 })
+        {
+            writer.Reset();
+            writer.WriteVarUInt36Small(value);
+
+            ByteReader reader = new(writer.ToArray());
+            Assert.Equal(value, reader.ReadVarUInt36Small());
+            Assert.Equal(0, reader.Remaining);
+        }
+
+        Assert.Throws<EncodingException>(() => writer.WriteVarUInt36Small(1UL 
<< 36));
+
+        writer.Reset();
+        writer.WriteVarUInt64(1UL << 36);
+        ByteReader overflowReader = new(writer.ToArray());
+        Assert.Throws<EncodingException>(() => 
overflowReader.ReadVarUInt36Small());
+    }
+
+    [Fact]
+    public void TaggedIntegersUseCompactAndWideEncodings()
+    {
+        AssertTaggedInt64(1_073_741_823L, expectedBytes: 4);
+        AssertTaggedInt64(1_073_741_824L, expectedBytes: 9);
+        AssertTaggedInt64(-1_073_741_824L, expectedBytes: 4);
+        AssertTaggedInt64(-1_073_741_825L, expectedBytes: 9);
+
+        AssertTaggedUInt64((ulong)int.MaxValue, expectedBytes: 4);
+        AssertTaggedUInt64((ulong)int.MaxValue + 1UL, expectedBytes: 9);
+        AssertTaggedUInt64(ulong.MaxValue, expectedBytes: 9);
+    }
+
+    [Fact]
+    public void SpanAndPatchOperationsMutateWrittenBytes()
+    {
+        ByteWriter writer = new();
+        Span<byte> span = writer.GetSpan(4);
+        span[0] = 10;
+        span[1] = 20;
+        span[2] = 30;
+        span[3] = 40;
+        writer.Advance(4);
+
+        writer.SetByte(1, 99);
+        writer.SetBytes(2, [7, 8]);
+
+        Assert.Equal(new byte[] { 10, 99, 7, 8 }, writer.ToArray());
+
+        writer.Reset();
+        Assert.Equal(0, writer.Count);
+    }
+
+    [Fact]
+    public void ReaderRejectsTruncatedVarInts()
+    {
+        Assert.Throws<OutOfBoundsException>(() => new 
ByteReader([0x80]).ReadVarUInt32());
+        Assert.Throws<OutOfBoundsException>(() => new 
ByteReader([0x80]).ReadVarUInt64());
+    }
+
+    private static void AssertTaggedInt64(long value, int expectedBytes)
+    {
+        ByteWriter writer = new();
+        writer.WriteTaggedInt64(value);
+        Assert.Equal(expectedBytes, writer.Count);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadTaggedInt64());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    private static void AssertTaggedUInt64(ulong value, int expectedBytes)
+    {
+        ByteWriter writer = new();
+        writer.WriteTaggedUInt64(value);
+        Assert.Equal(expectedBytes, writer.Count);
+
+        ByteReader reader = new(writer.ToArray());
+        Assert.Equal(value, reader.ReadTaggedUInt64());
+        Assert.Equal(0, reader.Remaining);
+    }
+}
diff --git a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs 
b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
index c2b4c07a9..355fcb628 100644
--- a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
+++ b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
@@ -115,6 +115,31 @@ public sealed class TwoStringFieldListHolder
     public List<TwoStringField?> Items { get; set; } = [];
 }
 
+[ForyObject]
+public sealed class OneStringFieldMapHolder
+{
+    public Dictionary<string, OneStringField?> Items { get; set; } = [];
+}
+
+[ForyObject]
+public sealed class TwoStringFieldMapHolder
+{
+    public Dictionary<string, TwoStringField?> Items { get; set; } = [];
+}
+
+[ForyObject]
+public sealed class UnsignedFields
+{
+    public byte U8 { get; set; }
+    public ushort U16 { get; set; }
+    public uint U32 { get; set; }
+    public ulong U64 { get; set; }
+    public byte? U8Nullable { get; set; }
+    public ushort? U16Nullable { get; set; }
+    public uint? U32Nullable { get; set; }
+    public ulong? U64Nullable { get; set; }
+}
+
 [ForyObject]
 public sealed class StructWithEnum
 {
@@ -162,6 +187,13 @@ public sealed class CollectionContainerHolder
     public Stack<int> StackField { get; set; } = new();
 }
 
+[ForyObject]
+public sealed class NestedTypedContainers
+{
+    public List<List<string>> NestedLists { get; set; } = [];
+    public Dictionary<string, List<Address?>> NestedMap { get; set; } = [];
+}
+
 public sealed class ForyRuntimeTests
 {
     private const ulong StringEncodingLatin1 = 0;
@@ -516,6 +548,40 @@ public sealed class ForyRuntimeTests
         Assert.Equal(source.StackField.ToArray(), 
decoded.StackField.ToArray());
     }
 
+    [Fact]
+    public void GeneratedSerializerSupportsNestedTypedContainers()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().TrackRef(true).Build();
+        fory.Register<Address>(452);
+        fory.Register<NestedTypedContainers>(453);
+
+        Address shared = new() { Street = "Main", Zip = 94107 };
+        NestedTypedContainers source = new()
+        {
+            NestedLists =
+            [
+                ["a", "b"],
+                ["c"],
+            ],
+            NestedMap = new Dictionary<string, List<Address?>>
+            {
+                ["first"] = [shared, null],
+                ["second"] = [shared],
+            },
+        };
+
+        NestedTypedContainers decoded = 
fory.Deserialize<NestedTypedContainers>(fory.Serialize(source));
+        Assert.Equal(source.NestedLists.Count, decoded.NestedLists.Count);
+        Assert.Equal(source.NestedLists[0], decoded.NestedLists[0]);
+        Assert.Equal(source.NestedLists[1], decoded.NestedLists[1]);
+
+        Assert.Equal(2, decoded.NestedMap["first"].Count);
+        Assert.Single(decoded.NestedMap["second"]);
+        Assert.NotNull(decoded.NestedMap["first"][0]);
+        Assert.Same(decoded.NestedMap["first"][0], 
decoded.NestedMap["second"][0]);
+        Assert.Null(decoded.NestedMap["first"][1]);
+    }
+
     [Fact]
     public void StreamDeserializeConsumesSingleFrame()
     {
@@ -746,6 +812,66 @@ public sealed class ForyRuntimeTests
         Assert.Equal(value.U64Tagged, decoded.U64Tagged);
     }
 
+    [Fact]
+    public void TaggedUnsignedFieldUsesCompactBoundary()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        fory.Register<EncodedNumbers>(301);
+
+        EncodedNumbers compact = new()
+        {
+            U32Fixed = 0x11223344u,
+            U64Tagged = (ulong)int.MaxValue,
+        };
+        EncodedNumbers wide = new()
+        {
+            U32Fixed = 0x11223344u,
+            U64Tagged = (ulong)int.MaxValue + 1UL,
+        };
+
+        byte[] compactPayload = fory.Serialize(compact);
+        byte[] widePayload = fory.Serialize(wide);
+
+        Assert.Equal(5, widePayload.Length - compactPayload.Length);
+        Assert.Equal(compact.U64Tagged, 
fory.Deserialize<EncodedNumbers>(compactPayload).U64Tagged);
+        Assert.Equal(wide.U64Tagged, 
fory.Deserialize<EncodedNumbers>(widePayload).U64Tagged);
+    }
+
+    [Theory]
+    [InlineData(false)]
+    [InlineData(true)]
+    public void UnsignedFieldsRoundTrip(bool compatible)
+    {
+        ForyRuntime fory = 
ForyRuntime.Builder().Compatible(compatible).Build();
+        fory.Register<UnsignedFields>(302);
+
+        UnsignedFields highValues = new()
+        {
+            U8 = byte.MaxValue,
+            U16 = ushort.MaxValue,
+            U32 = uint.MaxValue,
+            U64 = ulong.MaxValue,
+            U8Nullable = 128,
+            U16Nullable = 40_000,
+            U32Nullable = 4_000_000_000u,
+            U64Nullable = ulong.MaxValue - 7,
+        };
+        AssertUnsignedEqual(highValues, 
fory.Deserialize<UnsignedFields>(fory.Serialize(highValues)));
+
+        UnsignedFields nullablesMissing = new()
+        {
+            U8 = 0,
+            U16 = 1,
+            U32 = 2,
+            U64 = 3,
+            U8Nullable = null,
+            U16Nullable = null,
+            U32Nullable = null,
+            U64Nullable = null,
+        };
+        AssertUnsignedEqual(nullablesMissing, 
fory.Deserialize<UnsignedFields>(fory.Serialize(nullablesMissing)));
+    }
+
     [Fact]
     public void CompatibleSchemaEvolutionRoundTrip()
     {
@@ -849,6 +975,50 @@ public sealed class ForyRuntimeTests
         Assert.Equal("world", thirdRound.F1);
     }
 
+    [Theory]
+    [InlineData(false)]
+    [InlineData(true)]
+    public void CompatibleSchemaEvolutionRoundTripForMapValues(bool trackRef)
+    {
+        ForyRuntime writer = 
ForyRuntime.Builder().Compatible(true).TrackRef(trackRef).Build();
+        writer.Register<OneStringField>(200);
+        writer.Register<OneStringFieldMapHolder>(203);
+
+        ForyRuntime reader = 
ForyRuntime.Builder().Compatible(true).TrackRef(trackRef).Build();
+        reader.Register<TwoStringField>(200);
+        reader.Register<TwoStringFieldMapHolder>(203);
+
+        OneStringFieldMapHolder source = new()
+        {
+            Items = new Dictionary<string, OneStringField?>
+            {
+                ["first"] = new OneStringField { F1 = "hello" },
+                ["second"] = null,
+                ["third"] = new OneStringField { F1 = "world" },
+            },
+        };
+
+        TwoStringFieldMapHolder evolved = 
reader.Deserialize<TwoStringFieldMapHolder>(writer.Serialize(source));
+        Assert.Equal(3, evolved.Items.Count);
+        TwoStringField first = 
Assert.IsType<TwoStringField>(evolved.Items["first"]);
+        Assert.Equal("hello", first.F1);
+        Assert.Equal(string.Empty, first.F2);
+        Assert.Null(evolved.Items["second"]);
+        TwoStringField third = 
Assert.IsType<TwoStringField>(evolved.Items["third"]);
+        Assert.Equal("world", third.F1);
+        Assert.Equal(string.Empty, third.F2);
+
+        first.F2 = "extra-first";
+        third.F2 = "extra-third";
+        OneStringFieldMapHolder roundTripped = 
writer.Deserialize<OneStringFieldMapHolder>(reader.Serialize(evolved));
+        Assert.Equal(3, roundTripped.Items.Count);
+        OneStringField firstRound = 
Assert.IsType<OneStringField>(roundTripped.Items["first"]);
+        Assert.Equal("hello", firstRound.F1);
+        Assert.Null(roundTripped.Items["second"]);
+        OneStringField thirdRound = 
Assert.IsType<OneStringField>(roundTripped.Items["third"]);
+        Assert.Equal("world", thirdRound.F1);
+    }
+
     [Fact]
     public void SchemaVersionMismatchThrows()
     {
@@ -1046,6 +1216,57 @@ public sealed class ForyRuntimeTests
         Assert.Equal(1, nested[1]);
     }
 
+    [Fact]
+    public void GeneratedSerializerPreservesSharedDynamicReferences()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().TrackRef(true).Build();
+        fory.Register<DynamicAnyHolder>(401);
+
+        List<object?> shared = ["n", 1];
+        DynamicAnyHolder source = new()
+        {
+            AnyValue = shared,
+            AnySet = [shared],
+            AnyMap = new Dictionary<object, object?>
+            {
+                ["first"] = shared,
+                ["second"] = shared,
+            },
+        };
+
+        DynamicAnyHolder decoded = 
fory.Deserialize<DynamicAnyHolder>(fory.Serialize(source));
+        List<object?> anyValue = 
Assert.IsType<List<object?>>(decoded.AnyValue);
+        List<object?> setItem = 
Assert.Single(decoded.AnySet.OfType<List<object?>>());
+        List<object?> first = 
Assert.IsType<List<object?>>(decoded.AnyMap["first"]);
+        List<object?> second = 
Assert.IsType<List<object?>>(decoded.AnyMap["second"]);
+
+        Assert.Same(anyValue, setItem);
+        Assert.Same(anyValue, first);
+        Assert.Same(anyValue, second);
+    }
+
+    [Fact]
+    public void CollectionRefTrackingPreservesSharedValues()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().TrackRef(true).Build();
+        fory.Register<Address>(402);
+
+        Address shared = new() { Street = "Main", Zip = 94107 };
+
+        List<Address?> list = [shared, shared, null];
+        List<Address?> decodedList = 
fory.Deserialize<List<Address?>>(fory.Serialize(list));
+        Assert.Same(decodedList[0], decodedList[1]);
+        Assert.Null(decodedList[2]);
+
+        Dictionary<string, Address> map = new()
+        {
+            ["left"] = shared,
+            ["right"] = shared,
+        };
+        Dictionary<string, Address> decodedMap = 
fory.Deserialize<Dictionary<string, Address>>(fory.Serialize(map));
+        Assert.Same(decodedMap["left"], decodedMap["right"]);
+    }
+
     [Fact]
     public void StringSerializerUsesLatin1WhenAllCharsAreLatin1()
     {
@@ -1312,4 +1533,16 @@ public sealed class ForyRuntimeTests
         Assert.Equal(0, readContext.Reader.Remaining);
         return (encoding, decoded);
     }
+
+    private static void AssertUnsignedEqual(UnsignedFields expected, 
UnsignedFields actual)
+    {
+        Assert.Equal(expected.U8, actual.U8);
+        Assert.Equal(expected.U16, actual.U16);
+        Assert.Equal(expected.U32, actual.U32);
+        Assert.Equal(expected.U64, actual.U64);
+        Assert.Equal(expected.U8Nullable, actual.U8Nullable);
+        Assert.Equal(expected.U16Nullable, actual.U16Nullable);
+        Assert.Equal(expected.U32Nullable, actual.U32Nullable);
+        Assert.Equal(expected.U64Nullable, actual.U64Nullable);
+    }
 }
diff --git a/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs 
b/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs
new file mode 100644
index 000000000..f2fc81e3a
--- /dev/null
+++ b/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs
@@ -0,0 +1,335 @@
+// 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.
+
+using Apache.Fory;
+using ForyRuntime = Apache.Fory.Fory;
+
+namespace Apache.Fory.Tests;
+
+[ForyObject]
+public sealed class TimeEnvelope
+{
+    public DateOnly Date { get; set; }
+    public DateTime Timestamp { get; set; }
+    public DateTimeOffset OffsetTimestamp { get; set; }
+    public TimeSpan Duration { get; set; }
+    public List<DateOnly> Dates { get; set; } = [];
+    public List<DateTime> Timestamps { get; set; } = [];
+    public List<DateTimeOffset> OffsetTimestamps { get; set; } = [];
+    public List<TimeSpan> Durations { get; set; } = [];
+}
+
+[ForyObject]
+public sealed class NullableEnvelope
+{
+    public int? Int32Value { get; set; }
+    public ulong? UInt64Value { get; set; }
+    public DateTimeOffset? Timestamp { get; set; }
+    public TestColor? Color { get; set; }
+}
+
+[ForyObject]
+public sealed class CustomPayload
+{
+    public int Id { get; set; }
+    public string Marker { get; set; } = string.Empty;
+}
+
+public sealed class CustomPayloadSerializer : Serializer<CustomPayload>
+{
+    public override CustomPayload DefaultValue => null!;
+
+    public override void WriteData(WriteContext context, in CustomPayload 
value, bool hasGenerics)
+    {
+        _ = hasGenerics;
+        context.Writer.WriteVarInt32((value ?? new CustomPayload()).Id + 7);
+    }
+
+    public override CustomPayload ReadData(ReadContext context)
+    {
+        return new CustomPayload
+        {
+            Id = context.Reader.ReadVarInt32() - 7,
+            Marker = "custom",
+        };
+    }
+}
+
+public sealed class RuntimeEdgeCaseTests
+{
+    [Fact]
+    public void TimeRoundTripEdgeCases()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+
+        DateOnly date = new(1960, 2, 29);
+        Assert.Equal(date, fory.Deserialize<DateOnly>(fory.Serialize(date)));
+
+        DateTimeOffset offset = 
DateTimeOffset.FromUnixTimeMilliseconds(-1).AddTicks(45);
+        Assert.Equal(offset, 
fory.Deserialize<DateTimeOffset>(fory.Serialize(offset)));
+
+        TimeSpan duration = TimeSpan.FromDays(-3) - 
TimeSpan.FromMilliseconds(45) - TimeSpan.FromTicks(67);
+        Assert.Equal(duration, 
fory.Deserialize<TimeSpan>(fory.Serialize(duration)));
+
+        DateTime utc = new DateTime(2024, 1, 2, 3, 4, 5, 678, 
DateTimeKind.Utc).AddTicks(9);
+        AssertDateTimeEqual(utc, 
fory.Deserialize<DateTime>(fory.Serialize(utc)));
+
+        DateTime local = new DateTime(2024, 1, 2, 3, 4, 5, 678, 
DateTimeKind.Local).AddTicks(9);
+        AssertDateTimeEqual(local.ToUniversalTime(), 
fory.Deserialize<DateTime>(fory.Serialize(local)));
+
+        DateTime unspecified = DateTime.SpecifyKind(new DateTime(2024, 1, 2, 
3, 4, 5, 678).AddTicks(9), DateTimeKind.Unspecified);
+        AssertDateTimeEqual(
+            DateTime.SpecifyKind(unspecified, DateTimeKind.Utc),
+            fory.Deserialize<DateTime>(fory.Serialize(unspecified)));
+    }
+
+    [Fact]
+    public void TimeFieldsAndTypedListsRoundTrip()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        fory.Register<TimeEnvelope>(700);
+
+        TimeEnvelope source = new()
+        {
+            Date = new DateOnly(1969, 12, 31),
+            Timestamp = new DateTime(2024, 1, 2, 3, 4, 5, 678, 
DateTimeKind.Local).AddTicks(9),
+            OffsetTimestamp = new DateTimeOffset(2024, 1, 2, 3, 4, 5, 678, 
TimeSpan.FromHours(5)).AddTicks(9),
+            Duration = TimeSpan.FromTicks(-12_345_678_901),
+            Dates = [new DateOnly(1969, 12, 31), new DateOnly(1970, 1, 1), new 
DateOnly(2024, 4, 21)],
+            Timestamps =
+            [
+                new DateTime(2024, 1, 2, 3, 4, 5, 678, 
DateTimeKind.Utc).AddTicks(9),
+                new DateTime(2024, 1, 2, 3, 4, 5, 678, 
DateTimeKind.Local).AddTicks(10),
+                DateTime.SpecifyKind(new DateTime(2024, 1, 2, 3, 4, 5, 
678).AddTicks(11), DateTimeKind.Unspecified),
+            ],
+            OffsetTimestamps =
+            [
+                DateTimeOffset.FromUnixTimeMilliseconds(-1),
+                new DateTimeOffset(2024, 1, 2, 3, 4, 5, 678, 
TimeSpan.FromHours(-7)).AddTicks(12),
+            ],
+            Durations =
+            [
+                TimeSpan.Zero,
+                TimeSpan.FromTicks(123_456_789),
+                TimeSpan.FromTicks(-123_456_789),
+            ],
+        };
+
+        TimeEnvelope decoded = 
fory.Deserialize<TimeEnvelope>(fory.Serialize(source));
+        Assert.Equal(source.Date, decoded.Date);
+        AssertDateTimeEqual(source.Timestamp.ToUniversalTime(), 
decoded.Timestamp);
+        Assert.Equal(source.OffsetTimestamp, decoded.OffsetTimestamp);
+        Assert.Equal(source.Duration, decoded.Duration);
+        Assert.Equal(source.Dates, decoded.Dates);
+        Assert.Equal(source.OffsetTimestamps, decoded.OffsetTimestamps);
+        Assert.Equal(source.Durations, decoded.Durations);
+
+        Assert.Equal(source.Timestamps.Count, decoded.Timestamps.Count);
+        for (int i = 0; i < source.Timestamps.Count; i++)
+        {
+            AssertDateTimeEqual(NormalizeDateTime(source.Timestamps[i]), 
decoded.Timestamps[i]);
+        }
+    }
+
+    [Fact]
+    public void TimeSpanUsesVarIntSeconds()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        byte[] payload = fory.Serialize(TimeSpan.FromSeconds(1) + 
TimeSpan.FromTicks(3));
+
+        ByteReader reader = new(payload);
+        Assert.False(fory.ReadHead(reader));
+        Assert.Equal((sbyte)RefFlag.NotNullValue, reader.ReadInt8());
+        Assert.Equal((uint)TypeId.Duration, reader.ReadUInt8());
+        Assert.Equal(1L, reader.ReadVarInt64());
+        Assert.Equal(300, reader.ReadInt32());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Fact]
+    public void TimestampNormalizesNegativeFractionalSecond()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        byte[] payload = 
fory.Serialize(DateTimeOffset.FromUnixTimeMilliseconds(-1));
+
+        ByteReader reader = new(payload);
+        Assert.False(fory.ReadHead(reader));
+        Assert.Equal((sbyte)RefFlag.NotNullValue, reader.ReadInt8());
+        Assert.Equal((uint)TypeId.Timestamp, reader.ReadUInt8());
+        Assert.Equal(-1L, reader.ReadInt64());
+        Assert.Equal(999_000_000u, reader.ReadUInt32());
+        Assert.Equal(0, reader.Remaining);
+    }
+
+    [Fact]
+    public void NullableValuesRoundTrip()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        fory.Register<TestColor>(704);
+
+        Assert.Null(fory.Deserialize<int?>(fory.Serialize<int?>(null)));
+        Assert.Equal(123, fory.Deserialize<int?>(fory.Serialize<int?>(123)));
+        Assert.Equal(ulong.MaxValue, 
fory.Deserialize<ulong?>(fory.Serialize<ulong?>(ulong.MaxValue)));
+
+        DateTimeOffset timestamp = 
DateTimeOffset.FromUnixTimeMilliseconds(-1).AddTicks(23);
+        Assert.Equal(timestamp, 
fory.Deserialize<DateTimeOffset?>(fory.Serialize<DateTimeOffset?>(timestamp)));
+
+        
Assert.Null(fory.Deserialize<TestColor?>(fory.Serialize<TestColor?>(null)));
+        Assert.Equal(TestColor.Red, 
fory.Deserialize<TestColor?>(fory.Serialize<TestColor?>(TestColor.Red)));
+
+        List<int?> list = [null, 0, int.MaxValue];
+        Assert.Equal(list, fory.Deserialize<List<int?>>(fory.Serialize(list)));
+    }
+
+    [Fact]
+    public void NullableFieldsRoundTrip()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        fory.Register<TestColor>(705);
+        fory.Register<NullableEnvelope>(701);
+
+        NullableEnvelope populated = new()
+        {
+            Int32Value = int.MinValue,
+            UInt64Value = ulong.MaxValue,
+            Timestamp = 
DateTimeOffset.FromUnixTimeMilliseconds(-1).AddTicks(23),
+            Color = (TestColor)12345,
+        };
+        NullableEnvelope decodedPopulated = 
fory.Deserialize<NullableEnvelope>(fory.Serialize(populated));
+        Assert.Equal(populated.Int32Value, decodedPopulated.Int32Value);
+        Assert.Equal(populated.UInt64Value, decodedPopulated.UInt64Value);
+        Assert.Equal(populated.Timestamp, decodedPopulated.Timestamp);
+        Assert.Equal(populated.Color, decodedPopulated.Color);
+
+        NullableEnvelope missing = new();
+        NullableEnvelope decodedMissing = 
fory.Deserialize<NullableEnvelope>(fory.Serialize(missing));
+        Assert.Null(decodedMissing.Int32Value);
+        Assert.Null(decodedMissing.UInt64Value);
+        Assert.Null(decodedMissing.Timestamp);
+        Assert.Null(decodedMissing.Color);
+    }
+
+    [Fact]
+    public void CustomSerializerRegistrationByIdRoundTrip()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        fory.Register<CustomPayload, CustomPayloadSerializer>(702);
+
+        CustomPayload decoded = fory.Deserialize<CustomPayload>(
+            fory.Serialize(new CustomPayload { Id = 42, Marker = "ignored" }));
+
+        Assert.Equal(42, decoded.Id);
+        Assert.Equal("custom", decoded.Marker);
+    }
+
+    [Fact]
+    public void 
ThreadSafeCustomSerializerNamedRegistrationAppliesToInitializedLocal()
+    {
+        using ThreadSafeFory fory = ForyRuntime.Builder().BuildThreadSafe();
+        _ = fory.Serialize(1);
+        fory.Register<CustomPayload, CustomPayloadSerializer>(string.Empty, 
"custom_payload");
+
+        CustomPayload decoded = fory.Deserialize<CustomPayload>(
+            fory.Serialize(new CustomPayload { Id = 7, Marker = "ignored" }));
+
+        Assert.Equal(7, decoded.Id);
+        Assert.Equal("custom", decoded.Marker);
+    }
+
+    [Fact]
+    public void DeserializeRejectsTrailingBytes()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        byte[] payload = fory.Serialize(123);
+        byte[] invalidPayload = [.. payload, 0x7F];
+
+        InvalidDataException exception = 
Assert.Throws<InvalidDataException>(() => 
fory.Deserialize<int>(invalidPayload));
+        Assert.Contains("unexpected trailing bytes", exception.Message, 
StringComparison.Ordinal);
+    }
+
+    [Fact]
+    public void DeserializeRejectsNonXlangBitmap()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().Build();
+        byte[] payload = fory.Serialize(123);
+        payload[0] = 0;
+
+        InvalidDataException exception = 
Assert.Throws<InvalidDataException>(() => fory.Deserialize<int>(payload));
+        Assert.Contains("xlang bitmap mismatch", exception.Message, 
StringComparison.Ordinal);
+    }
+
+    [Fact]
+    public void DynamicAnyRejectsUnknownUserTypeId()
+    {
+        ForyRuntime writer = ForyRuntime.Builder().Build();
+        writer.Register<CustomPayload, CustomPayloadSerializer>(703);
+        byte[] payload = writer.Serialize<object?>(new CustomPayload { Id = 9, 
Marker = "ignored" });
+        byte[] invalidPayload = RewriteRootUserTypeId(payload, TypeId.Ext, 
704);
+
+        ForyRuntime reader = ForyRuntime.Builder().Build();
+        TypeNotRegisteredException exception =
+            Assert.Throws<TypeNotRegisteredException>(() => 
reader.Deserialize<object?>(invalidPayload));
+        Assert.Contains("user_type_id=704", exception.Message, 
StringComparison.Ordinal);
+    }
+
+    [Fact]
+    public void ThreadSafeForyThrowsAfterDispose()
+    {
+        ThreadSafeFory fory = ForyRuntime.Builder().BuildThreadSafe();
+        byte[] payload = fory.Serialize(123);
+        fory.Dispose();
+
+        Assert.Throws<ObjectDisposedException>(() => fory.Serialize(1));
+        Assert.Throws<ObjectDisposedException>(() => 
fory.Deserialize<int>(payload));
+        Assert.Throws<ObjectDisposedException>(() => fory.Register<Node>(999));
+    }
+
+    private static DateTime NormalizeDateTime(DateTime value)
+    {
+        return value.Kind switch
+        {
+            DateTimeKind.Utc => value,
+            DateTimeKind.Local => value.ToUniversalTime(),
+            _ => DateTime.SpecifyKind(value, DateTimeKind.Utc),
+        };
+    }
+
+    private static void AssertDateTimeEqual(DateTime expected, DateTime actual)
+    {
+        Assert.Equal(expected, actual);
+        Assert.Equal(DateTimeKind.Utc, actual.Kind);
+    }
+
+    private static byte[] RewriteRootUserTypeId(byte[] payload, TypeId 
expectedWireTypeId, uint replacementUserTypeId)
+    {
+        ByteReader reader = new(payload);
+        _ = reader.ReadUInt8(); // frame header bitmap
+        _ = reader.ReadInt8(); // root ref flag
+        uint wireTypeId = reader.ReadUInt8();
+        Assert.Equal((uint)expectedWireTypeId, wireTypeId);
+
+        int userTypeIdStart = reader.Cursor;
+        _ = reader.ReadVarUInt32();
+        int userTypeIdEnd = reader.Cursor;
+
+        ByteWriter writer = new(payload.Length + 5);
+        writer.WriteBytes(payload.AsSpan(0, userTypeIdStart));
+        writer.WriteVarUInt32(replacementUserTypeId);
+        writer.WriteBytes(payload.AsSpan(userTypeIdEnd));
+        return writer.ToArray();
+    }
+}
diff --git a/csharp/tests/Fory.Tests/StringSerializerTests.cs 
b/csharp/tests/Fory.Tests/StringSerializerTests.cs
new file mode 100644
index 000000000..1b9ca4bf8
--- /dev/null
+++ b/csharp/tests/Fory.Tests/StringSerializerTests.cs
@@ -0,0 +1,127 @@
+// 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.
+
+using Apache.Fory;
+
+namespace Apache.Fory.Tests;
+
+public sealed class StringSerializerTests
+{
+    private const ulong Latin1 = 0;
+    private const ulong Utf16 = 1;
+    private const ulong Utf8 = 2;
+
+    [Fact]
+    public void StringRoundTripEdgeCases()
+    {
+        string[] values =
+        [
+            string.Empty,
+            "\0",
+            "\n\r\t",
+            "\u00FF",
+            "\u0100",
+            "café",
+            "你好",
+            "abc你好",
+            "hello café 你好",
+            "😀😁",
+            new string('\u00E9', 64),
+            new string('你', 64),
+        ];
+
+        foreach (string value in values)
+        {
+            (_, string decoded, _, _) = WriteAndReadString(value);
+            Assert.Equal(value, decoded);
+        }
+    }
+
+    [Fact]
+    public void StringSerializerHandlesHeaderSizeBoundaries()
+    {
+        AssertHeader(new string('a', 31), Latin1, expectedHeaderBytes: 1);
+        AssertHeader(new string('a', 32), Latin1, expectedHeaderBytes: 2);
+
+        AssertHeader(new string('你', 15), Utf16, expectedHeaderBytes: 1);
+        AssertHeader(new string('你', 16), Utf16, expectedHeaderBytes: 2);
+
+        AssertHeader(new string('a', 28) + "你", Utf8, expectedHeaderBytes: 1);
+        AssertHeader(new string('a', 29) + "你", Utf8, expectedHeaderBytes: 2);
+
+        AssertHeader(new string('a', 4092) + "你", Utf8, expectedHeaderBytes: 
2);
+        AssertHeader(new string('a', 4093) + "你", Utf8, expectedHeaderBytes: 
3);
+    }
+
+    [Fact]
+    public void StringSerializerRejectsOddUtf16Payload()
+    {
+        ByteWriter writer = new();
+        writer.WriteVarUInt36Small((3UL << 2) | Utf16);
+        writer.WriteBytes([0x61, 0x00, 0x62]);
+
+        ReadContext context = new(new ByteReader(writer.ToArray()), new 
TypeResolver(), trackRef: false, compatible: false);
+        Assert.Throws<EncodingException>(() => 
StringSerializer.ReadString(context));
+    }
+
+    [Fact]
+    public void StringSerializerRejectsUnknownEncoding()
+    {
+        ByteWriter writer = new();
+        writer.WriteVarUInt36Small(3UL);
+
+        ReadContext context = new(new ByteReader(writer.ToArray()), new 
TypeResolver(), trackRef: false, compatible: false);
+        Assert.Throws<EncodingException>(() => 
StringSerializer.ReadString(context));
+    }
+
+    private static void AssertHeader(string value, ulong expectedEncoding, int 
expectedHeaderBytes)
+    {
+        (ulong encoding, string decoded, int headerBytes, int byteLength) = 
WriteAndReadString(value);
+        Assert.Equal(expectedEncoding, encoding);
+        Assert.Equal(expectedHeaderBytes, headerBytes);
+        Assert.Equal(value, decoded);
+        Assert.Equal(byteLength + headerBytes, GetPayloadLength(value));
+    }
+
+    private static int GetPayloadLength(string value)
+    {
+        ByteWriter writer = new();
+        WriteContext context = new(writer, new TypeResolver(), trackRef: 
false, compatible: false);
+        StringSerializer.WriteString(context, value);
+        return writer.Count;
+    }
+
+    private static (ulong Encoding, string Decoded, int HeaderBytes, int 
ByteLength) WriteAndReadString(string value)
+    {
+        ByteWriter writer = new();
+        TypeResolver resolver = new();
+        WriteContext writeContext = new(writer, resolver, trackRef: false, 
compatible: false);
+        StringSerializer.WriteString(writeContext, value);
+
+        byte[] payload = writer.ToArray();
+        ByteReader headerReader = new(payload);
+        ulong header = headerReader.ReadVarUInt36Small();
+        ulong encoding = header & 0x03;
+        int byteLength = checked((int)(header >> 2));
+        Assert.Equal(payload.Length - headerReader.Cursor, byteLength);
+
+        ReadContext readContext = new(new ByteReader(payload), resolver, 
trackRef: false, compatible: false);
+        string decoded = StringSerializer.ReadString(readContext);
+        Assert.Equal(0, readContext.Reader.Remaining);
+        return (encoding, decoded, headerReader.Cursor, byteLength);
+    }
+}
diff --git a/swift/Sources/Fory/AnySerializer.swift 
b/swift/Sources/Fory/AnySerializer.swift
index cd2fda808..3b64fece8 100644
--- a/swift/Sources/Fory/AnySerializer.swift
+++ b/swift/Sources/Fory/AnySerializer.swift
@@ -215,13 +215,7 @@ struct SerializableAny: Serializer {
                 context.buffer.writeInt8(RefFlag.null.rawValue)
                 return
             }
-            if refMode == .tracking, anyValueIsRefType(value), let object = 
value as AnyObject? {
-                if context.refWriter.tryWriteRef(buffer: context.buffer, 
object: object) {
-                    return
-                }
-            } else {
-                context.buffer.writeInt8(RefFlag.notNullValue.rawValue)
-            }
+            context.buffer.writeInt8(RefFlag.notNullValue.rawValue)
         }
 
         if writeTypeInfo {
@@ -273,7 +267,11 @@ struct SerializableAny: Serializer {
                 let remoteTypeInfo = try requireDynamicTypeInfo()
                 let value = try foryReadCompatibleData(context, 
remoteTypeInfo: remoteTypeInfo)
                 if let reservedRefID {
-                    context.refReader.storeRef(value, at: reservedRefID)
+                    if let object = value.value as AnyObject? {
+                        context.refReader.storeRef(object, at: reservedRefID)
+                    } else {
+                        context.refReader.storeRef(value, at: reservedRefID)
+                    }
                 }
                 return value
             case .notNullValue:
@@ -296,13 +294,6 @@ private func unwrapOptionalAny(_ value: Any) -> Any? {
     return child
 }
 
-private func anyValueIsRefType(_ value: Any) -> Bool {
-    guard let serializer = value as? any Serializer else {
-        return false
-    }
-    return type(of: serializer).isRefType
-}
-
 private func toAnyHashableKey(_ value: Any) throws -> AnyHashable {
     if let anyHashable = value as? AnyHashable {
         return anyHashable
@@ -339,7 +330,16 @@ private func writeAnyPayload(_ value: Any, context: 
WriteContext, hasGenerics: B
     defer { context.leaveDynamicAnyDepth() }
 
     if let serializer = value as? any Serializer {
-        try serializer.foryWriteData(context, hasGenerics: hasGenerics)
+        if type(of: serializer).isRefType {
+            try serializer.foryWrite(
+                context,
+                refMode: .tracking,
+                writeTypeInfo: false,
+                hasGenerics: hasGenerics
+            )
+        } else {
+            try serializer.foryWriteData(context, hasGenerics: hasGenerics)
+        }
         return
     }
     if let list = value as? [Any] {
diff --git a/swift/Sources/Fory/TypeResolver.swift 
b/swift/Sources/Fory/TypeResolver.swift
index e0f49ab50..57b795ab7 100644
--- a/swift/Sources/Fory/TypeResolver.swift
+++ b/swift/Sources/Fory/TypeResolver.swift
@@ -144,6 +144,48 @@ private func encodedTypeDefHasUserTypeFields(_ fields: 
[TypeMeta.FieldInfo]) ->
     fields.contains { fieldNeedsTypeInfo($0.fieldType) }
 }
 
+@inline(__always)
+private func readRegisteredValue<T: Serializer>(_ context: ReadContext, as 
type: T.Type) throws -> T {
+    try T.foryRead(
+        context,
+        refMode: T.isRefType ? .tracking : .none,
+        readTypeInfo: false
+    )
+}
+
+@inline(__always)
+private func readCompatibleRegisteredValue<T: Serializer>(
+    _ context: ReadContext,
+    as type: T.Type,
+    remoteTypeInfo: TypeInfo
+) throws -> T {
+    guard T.isRefType else {
+        return try T.foryReadCompatibleData(context, remoteTypeInfo: 
remoteTypeInfo)
+    }
+
+    let rawFlag = try context.buffer.readInt8()
+    guard let flag = RefFlag(rawValue: rawFlag) else {
+        throw ForyError.refError("invalid ref flag \(rawFlag)")
+    }
+
+    switch flag {
+    case .null:
+        return T.foryDefault()
+    case .ref:
+        let refID = try context.buffer.readVarUInt32()
+        return try context.refReader.readRef(refID, as: T.self)
+    case .refValue:
+        let reservedRefID = context.trackRef ? 
context.refReader.reserveRefID() : nil
+        let value = try T.foryReadCompatibleData(context, remoteTypeInfo: 
remoteTypeInfo)
+        if let reservedRefID, let object = value as AnyObject? {
+            context.refReader.storeRef(object, at: reservedRefID)
+        }
+        return value
+    case .notNullValue:
+        return try T.foryReadCompatibleData(context, remoteTypeInfo: 
remoteTypeInfo)
+    }
+}
+
 public final class TypeInfo: @unchecked Sendable {
     static let uncached = TypeInfo(typeID: .unknown)
 
@@ -411,10 +453,10 @@ final class TypeResolver {
             typeName: MetaString.empty(specialChar1: "$", specialChar2: "_"),
             fields: T.foryFieldsInfo(trackRef: trackRef),
             reader: { context in
-                try T.foryRead(context, refMode: .none, readTypeInfo: false)
+                try readRegisteredValue(context, as: T.self)
             },
             compatibleReader: { context, remoteTypeInfo in
-                try T.foryReadCompatibleData(context, remoteTypeInfo: 
remoteTypeInfo)
+                try readCompatibleRegisteredValue(context, as: T.self, 
remoteTypeInfo: remoteTypeInfo)
             }
         )
 
@@ -462,10 +504,10 @@ final class TypeResolver {
             typeName: typeNameMeta,
             fields: T.foryFieldsInfo(trackRef: trackRef),
             reader: { context in
-                try T.foryRead(context, refMode: .none, readTypeInfo: false)
+                try readRegisteredValue(context, as: T.self)
             },
             compatibleReader: { context, remoteTypeInfo in
-                try T.foryReadCompatibleData(context, remoteTypeInfo: 
remoteTypeInfo)
+                try readCompatibleRegisteredValue(context, as: T.self, 
remoteTypeInfo: remoteTypeInfo)
             }
         )
 
diff --git a/swift/Tests/ForyTests/AnyTests.swift 
b/swift/Tests/ForyTests/AnyTests.swift
index 278d2e2d8..c3d0433b6 100644
--- a/swift/Tests/ForyTests/AnyTests.swift
+++ b/swift/Tests/ForyTests/AnyTests.swift
@@ -41,6 +41,19 @@ private final class AnyObjectDynamicNode {
     }
 }
 
+@ForyObject
+private final class AnyObjectDynamicGraphNode {
+    var value: Int32 = 0
+    var next: AnyObjectDynamicGraphNode?
+
+    required init() {}
+
+    init(value: Int32, next: AnyObjectDynamicGraphNode? = nil) {
+        self.value = value
+        self.next = next
+    }
+}
+
 @ForyObject
 private struct AnyHashableMapHolder {
     var map: [AnyHashable: Any] = [:]
@@ -409,6 +422,39 @@ func topLevelAnyHomogeneousListAndMapRoundTrip() throws {
     #expect(map?["k2"] as? String == "v2")
 }
 
+@Test
+func dynamicAnyListTracksRefs() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true))
+    fory.register(AnyObjectDynamicGraphNode.self, id: 503)
+
+    let shared = AnyObjectDynamicGraphNode(value: 17)
+    let payload = try fory.serialize([shared, shared] as [Any])
+    let decoded: Any = try fory.deserialize(payload)
+    let list = decoded as? [Any]
+    let first = list?.first as? AnyObjectDynamicGraphNode
+    let second = list?.dropFirst().first as? AnyObjectDynamicGraphNode
+
+    #expect(list?.count == 2)
+    #expect(first != nil)
+    #expect(first === second)
+}
+
+@Test
+func dynamicAnyObjectTracksCycle() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true))
+    fory.register(AnyObjectDynamicGraphNode.self, id: 504)
+
+    let node = AnyObjectDynamicGraphNode(value: 21)
+    node.next = node
+
+    let decoded: AnyObject = try fory.deserialize(try fory.serialize(node as 
AnyObject))
+    let graphNode = decoded as? AnyObjectDynamicGraphNode
+
+    #expect(graphNode != nil)
+    #expect(graphNode?.value == 21)
+    #expect(graphNode?.next === graphNode)
+}
+
 @Test
 func dynamicAnyMaxDepthRejectsDeepNesting() throws {
     let value = nestedDynamicAnyList(depth: 3)
diff --git a/swift/Tests/ForyTests/ByteBufferTests.swift 
b/swift/Tests/ForyTests/ByteBufferTests.swift
new file mode 100644
index 000000000..fc26f7564
--- /dev/null
+++ b/swift/Tests/ForyTests/ByteBufferTests.swift
@@ -0,0 +1,230 @@
+// 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.
+
+import Foundation
+import Testing
+@testable import Fory
+
+@Test
+func byteBufferPrimitiveReadWriteAndCursorOps() throws {
+    let buffer = ByteBuffer(capacity: 8)
+    buffer.writeUInt8(0xFE)
+    buffer.writeInt8(-12)
+    buffer.writeInt16(Int16.max)
+    buffer.writeInt32(Int32.min)
+    buffer.writeInt64(Int64.max)
+    buffer.writeFloat32(3.25)
+    buffer.writeFloat64(-123.5)
+    buffer.writeBytes([0x01, 0x02, 0x03, 0x04])
+
+    #expect(buffer.count == 32)
+    #expect(buffer.getCursor() == 0)
+
+    #expect(try buffer.readUInt8() == 0xFE)
+    #expect(try buffer.readInt8() == -12)
+    #expect(try buffer.readInt16() == Int16.max)
+
+    let int32Offset = buffer.getCursor()
+    #expect(try buffer.readInt32() == Int32.min)
+    buffer.moveBack(4)
+    #expect(buffer.getCursor() == int32Offset)
+    #expect(try buffer.readInt32() == Int32.min)
+
+    #expect(try buffer.readInt64() == Int64.max)
+    #expect(try buffer.readFloat32() == 3.25)
+    #expect(try buffer.readFloat64() == -123.5)
+    #expect(try buffer.readBytes(count: 4) == [0x01, 0x02, 0x03, 0x04])
+    #expect(buffer.remaining == 0)
+}
+
+@Test
+func byteBufferReplaceMutationAndSnapshots() throws {
+    let buffer = ByteBuffer(bytes: [0x01, 0x02, 0x03, 0x04])
+    _ = try buffer.readUInt8()
+    _ = try buffer.readUInt8()
+    #expect(buffer.getCursor() == 2)
+
+    buffer.replace(with: Data([0x10, 0x11, 0x12]))
+    #expect(buffer.getCursor() == 0)
+    #expect(buffer.count == 3)
+    #expect(Array(buffer.toData()) == [0x10, 0x11, 0x12])
+
+    let bridged = Array(buffer.copyToData())
+    buffer.setByte(at: 1, to: 0xFE)
+    buffer.setBytes(at: 0, to: [0xAA, 0xBB])
+
+    #expect(buffer.storage == [0xAA, 0xBB, 0x12])
+    #expect(bridged == [0x10, 0x11, 0x12])
+
+    buffer.setCursor(buffer.count)
+    buffer.flip()
+    #expect(buffer.getCursor() == 0)
+
+    buffer.clear()
+    #expect(buffer.count == 0)
+    #expect(buffer.remaining == 0)
+
+    buffer.writeBytes([0x21, 0x22])
+    buffer.reset()
+    #expect(buffer.count == 0)
+    #expect(buffer.getCursor() == 0)
+}
+
+@Test
+func byteBufferVarUIntBoundariesUseExpectedSizes() throws {
+    let values32: [UInt32] = [
+        0,
+        1,
+        1 << 6,
+        1 << 7,
+        1 << 13,
+        1 << 14,
+        1 << 20,
+        1 << 21,
+        1 << 27,
+        1 << 28,
+        UInt32.max
+    ]
+    let values64: [UInt64] = [
+        0,
+        1,
+        1 << 6,
+        1 << 7,
+        1 << 13,
+        1 << 14,
+        1 << 20,
+        1 << 21,
+        1 << 27,
+        1 << 28,
+        1 << 35,
+        1 << 42,
+        1 << 49,
+        1 << 56,
+        UInt64.max
+    ]
+
+    let buffer = ByteBuffer()
+    for value in values32 {
+        let start = buffer.count
+        buffer.writeVarUInt32(value)
+        #expect(buffer.count - start == UnsafeUtil.varUInt32Size(value))
+    }
+    for value in values64 {
+        let start = buffer.count
+        buffer.writeVarUInt64(value)
+        #expect(buffer.count - start == UnsafeUtil.varUInt64Size(value))
+    }
+
+    for value in values32 {
+        #expect(try buffer.readVarUInt32() == value)
+    }
+    for value in values64 {
+        #expect(try buffer.readVarUInt64() == value)
+    }
+    #expect(buffer.remaining == 0)
+}
+
+@Test
+func byteBufferVarIntAndTaggedIntegerBoundariesRoundTrip() throws {
+    let int32Values: [Int32] = [Int32.min, -1_000_000, -1, 0, 1, 127, 
Int32.max]
+    let int64Values: [Int64] = [Int64.min, -1_000_000_000_000, -1, 0, 1, 127, 
Int64.max]
+    let taggedIntValues: [(Int64, Int)] = [
+        (-1_073_741_824, 4),
+        (-1_073_741_823, 4),
+        (-1, 4),
+        (0, 4),
+        (1_073_741_823, 4),
+        (1_073_741_824, 9),
+        (Int64.min, 9),
+        (Int64.max, 9)
+    ]
+    let taggedUIntValues: [(UInt64, Int)] = [
+        (0, 4),
+        (1, 4),
+        (UInt64(Int32.max), 4),
+        (UInt64(Int32.max) + 1, 9),
+        (UInt64.max, 9)
+    ]
+
+    let buffer = ByteBuffer()
+    for value in int32Values {
+        buffer.writeVarInt32(value)
+    }
+    for value in int64Values {
+        buffer.writeVarInt64(value)
+    }
+    for (value, encodedWidth) in taggedIntValues {
+        let start = buffer.count
+        buffer.writeTaggedInt64(value)
+        #expect(buffer.count - start == encodedWidth)
+    }
+    for (value, encodedWidth) in taggedUIntValues {
+        let start = buffer.count
+        buffer.writeTaggedUInt64(value)
+        #expect(buffer.count - start == encodedWidth)
+    }
+
+    for value in int32Values {
+        #expect(try buffer.readVarInt32() == value)
+    }
+    for value in int64Values {
+        #expect(try buffer.readVarInt64() == value)
+    }
+    for (value, _) in taggedIntValues {
+        #expect(try buffer.readTaggedInt64() == value)
+    }
+    for (value, _) in taggedUIntValues {
+        #expect(try buffer.readTaggedUInt64() == value)
+    }
+    #expect(buffer.remaining == 0)
+}
+
+@Test
+func byteBufferRejectsInvalidVarintsAndUtf8() throws {
+    let varUInt32Overflow = ByteBuffer(bytes: [0x80, 0x80, 0x80, 0x80, 0x80])
+    do {
+        _ = try varUInt32Overflow.readVarUInt32()
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("varuint32 overflow"))
+    }
+
+    let truncatedVarUInt64 = ByteBuffer(bytes: [0x80, 0x80, 0x80, 0x80, 0x80, 
0x80, 0x80, 0x80])
+    do {
+        _ = try truncatedVarUInt64.readVarUInt64()
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("out of bounds"))
+    }
+
+    let varUInt36Overflow = ByteBuffer()
+    varUInt36Overflow.writeVarUInt64(1 << 36)
+    do {
+        _ = try varUInt36Overflow.readVarUInt36Small()
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("varuint36small overflow"))
+    }
+
+    let invalidUTF8 = ByteBuffer(bytes: [0xC3, 0x28])
+    do {
+        _ = try invalidUTF8.readUTF8String(count: 2)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("invalid UTF-8"))
+    }
+}
diff --git a/swift/Tests/ForyTests/CollectionSerializerTests.swift 
b/swift/Tests/ForyTests/CollectionSerializerTests.swift
new file mode 100644
index 000000000..3345fdf19
--- /dev/null
+++ b/swift/Tests/ForyTests/CollectionSerializerTests.swift
@@ -0,0 +1,313 @@
+// 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.
+
+import Foundation
+import Testing
+@testable import Fory
+
+@ForyObject
+private final class RefKeyNode: Hashable {
+    var id: Int32 = 0
+
+    required init() {}
+
+    init(id: Int32) {
+        self.id = id
+    }
+
+    static func == (lhs: RefKeyNode, rhs: RefKeyNode) -> Bool {
+        lhs === rhs
+    }
+
+    func hash(into hasher: inout Hasher) {
+        hasher.combine(ObjectIdentifier(self))
+    }
+}
+
+@ForyObject
+private struct RefKeyHolder {
+    var key: RefKeyNode = .init()
+    var map: [RefKeyNode: Int32] = [:]
+}
+
+@ForyObject
+private struct RefKeyValueHolder {
+    var map: [RefKeyNode: RefKeyNode] = [:]
+}
+
+@ForyObject
+private struct RefKeyChunkHolder {
+    var keys: [RefKeyNode] = []
+    var map: [RefKeyNode: Int32] = [:]
+}
+
+@Test
+func primitiveArrayTypeIDsAndRoundTripsCoverSwiftCollectionSurface() throws {
+    #expect([Bool].staticTypeId == .boolArray)
+    #expect([Int8].staticTypeId == .int8Array)
+    #expect([Int16].staticTypeId == .int16Array)
+    #expect([Int32].staticTypeId == .int32Array)
+    #expect([Int64].staticTypeId == .int64Array)
+    #expect([UInt8].staticTypeId == .uint8Array)
+    #expect([UInt16].staticTypeId == .uint16Array)
+    #expect([UInt32].staticTypeId == .uint32Array)
+    #expect([UInt64].staticTypeId == .uint64Array)
+    #expect([Float16].staticTypeId == .float16Array)
+    #expect([BFloat16].staticTypeId == .bfloat16Array)
+    #expect([Float].staticTypeId == .float32Array)
+    #expect([Double].staticTypeId == .float64Array)
+
+    let fory = Fory()
+
+    let bools = [true, false, true]
+    let int8s: [Int8] = [-1, 0, 127]
+    let int16s: [Int16] = [Int16.min, -1, Int16.max]
+    let int32s: [Int32] = [Int32.min, 0, Int32.max]
+    let int64s: [Int64] = [Int64.min, 0, Int64.max]
+    let uint8s: [UInt8] = [0, 1, 255]
+    let uint16s: [UInt16] = [0, 1, UInt16.max]
+    let uint32s: [UInt32] = [0, 1, UInt32.max]
+    let uint64s: [UInt64] = [0, 1, UInt64.max]
+    let float16s: [Float16] = [Float16(0), Float16(-2.5), Float16(7.25)]
+    let bfloat16s: [BFloat16] = [.init(rawValue: 0x0000), .init(rawValue: 
0x3F80), .init(rawValue: 0xBF80)]
+    let floats: [Float] = [-1.5, 0, 3.25]
+    let doubles: [Double] = [-10.5, 0, 9.75]
+
+    let decodedBools: [Bool] = try fory.deserialize(try fory.serialize(bools))
+    let decodedInt8s: [Int8] = try fory.deserialize(try fory.serialize(int8s))
+    let decodedInt16s: [Int16] = try fory.deserialize(try 
fory.serialize(int16s))
+    let decodedInt32s: [Int32] = try fory.deserialize(try 
fory.serialize(int32s))
+    let decodedInt64s: [Int64] = try fory.deserialize(try 
fory.serialize(int64s))
+    let decodedUInt8s: [UInt8] = try fory.deserialize(try 
fory.serialize(uint8s))
+    let decodedUInt16s: [UInt16] = try fory.deserialize(try 
fory.serialize(uint16s))
+    let decodedUInt32s: [UInt32] = try fory.deserialize(try 
fory.serialize(uint32s))
+    let decodedUInt64s: [UInt64] = try fory.deserialize(try 
fory.serialize(uint64s))
+    let decodedFloat16s: [Float16] = try fory.deserialize(try 
fory.serialize(float16s))
+    let decodedBFloat16s: [BFloat16] = try fory.deserialize(try 
fory.serialize(bfloat16s))
+    let decodedFloats: [Float] = try fory.deserialize(try 
fory.serialize(floats))
+    let decodedDoubles: [Double] = try fory.deserialize(try 
fory.serialize(doubles))
+
+    #expect(decodedBools == bools)
+    #expect(decodedInt8s == int8s)
+    #expect(decodedInt16s == int16s)
+    #expect(decodedInt32s == int32s)
+    #expect(decodedInt64s == int64s)
+    #expect(decodedUInt8s == uint8s)
+    #expect(decodedUInt16s == uint16s)
+    #expect(decodedUInt32s == uint32s)
+    #expect(decodedUInt64s == uint64s)
+    #expect(decodedFloat16s.map(\.bitPattern) == float16s.map(\.bitPattern))
+    #expect(decodedBFloat16s == bfloat16s)
+    #expect(decodedFloats == floats)
+    #expect(decodedDoubles == doubles)
+}
+
+@Test
+func floatingPointArraysPreserveBits() throws {
+    let fory = Fory()
+
+    let float16s: [Float16] = [
+        .init(bitPattern: 0x0000),
+        .init(bitPattern: 0x8000),
+        .init(bitPattern: 0x7C00),
+        .init(bitPattern: 0xFC00),
+        .init(bitPattern: 0x0001),
+        .init(bitPattern: 0x7E21)
+    ]
+    let bfloat16s: [BFloat16] = [
+        .init(rawValue: 0x0000),
+        .init(rawValue: 0x8000),
+        .init(rawValue: 0x7F80),
+        .init(rawValue: 0xFF80),
+        .init(rawValue: 0x0001),
+        .init(rawValue: 0x7FC1)
+    ]
+    let floats: [Float] = [
+        0.0,
+        -0.0,
+        .infinity,
+        -.infinity,
+        .leastNonzeroMagnitude,
+        .greatestFiniteMagnitude,
+        Float(bitPattern: 0x7FC0_1234)
+    ]
+    let doubles: [Double] = [
+        0.0,
+        -0.0,
+        .infinity,
+        -.infinity,
+        .leastNonzeroMagnitude,
+        .greatestFiniteMagnitude,
+        Double(bitPattern: 0x7FF8_0000_0000_1234)
+    ]
+
+    let decodedFloat16s: [Float16] = try fory.deserialize(try 
fory.serialize(float16s))
+    let decodedBFloat16s: [BFloat16] = try fory.deserialize(try 
fory.serialize(bfloat16s))
+    let decodedFloats: [Float] = try fory.deserialize(try 
fory.serialize(floats))
+    let decodedDoubles: [Double] = try fory.deserialize(try 
fory.serialize(doubles))
+
+    #expect(decodedFloat16s.map(\.bitPattern) == float16s.map(\.bitPattern))
+    #expect(decodedBFloat16s.map(\.rawValue) == bfloat16s.map(\.rawValue))
+    #expect(decodedFloats.map(\.bitPattern) == floats.map(\.bitPattern))
+    #expect(decodedDoubles.map(\.bitPattern) == doubles.map(\.bitPattern))
+}
+
+@Test
+func uint8ArrayAndDataInteroperateAcrossWireTypes() throws {
+    let payload: [UInt8] = [0x00, 0x01, 0x7F, 0xFF]
+
+    let schemaConsistent = Fory(config: .init(xlang: true, trackRef: false, 
compatible: false))
+    let schemaBytes = try schemaConsistent.serialize(payload)
+    #expect(Array(schemaBytes)[2] == UInt8(TypeId.uint8Array.rawValue))
+
+    let compatible = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    let compatibleBytes = try compatible.serialize(payload)
+    #expect(Array(compatibleBytes)[2] == UInt8(TypeId.binary.rawValue))
+
+    let decodedBinary: Data = try compatible.deserialize(compatibleBytes)
+    #expect(Array(decodedBinary) == payload)
+
+    let binary = Data(payload)
+    let binaryBytes = try compatible.serialize(binary)
+    let decodedArray: [UInt8] = try compatible.deserialize(binaryBytes)
+    #expect(decodedArray == payload)
+}
+
+@Test
+func nestedCollectionsAndNullabilityRoundTrip() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true, compatible: 
true))
+
+    let nested: [[String?]] = [
+        ["alpha", nil],
+        [],
+        ["beta", "gamma"]
+    ]
+    let decodedNested: [[String?]] = try fory.deserialize(try 
fory.serialize(nested))
+    #expect(decodedNested == nested)
+
+    let set: Set<UInt16> = [0, 42, UInt16.max]
+    let decodedSet: Set<UInt16> = try fory.deserialize(try fory.serialize(set))
+    #expect(decodedSet == set)
+
+    let map: [Int32?: [String?]?] = [
+        1: ["x", nil, "z"],
+        nil: nil,
+        3: []
+    ]
+    let decodedMap: [Int32?: [String?]?] = try fory.deserialize(try 
fory.serialize(map))
+    #expect(decodedMap == map)
+}
+
+@Test
+func mapRefKeysTrackIdentity() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true, compatible: 
true))
+    fory.register(RefKeyNode.self, id: 9501)
+    fory.register(RefKeyHolder.self, id: 9502)
+
+    let sharedKey = RefKeyNode(id: 7)
+    let value = RefKeyHolder(key: sharedKey, map: [sharedKey: 99])
+
+    let decoded: RefKeyHolder = try fory.deserialize(try fory.serialize(value))
+    let pair = decoded.map.first
+
+    #expect(pair != nil)
+    if let pair {
+        #expect(decoded.key === pair.key)
+        #expect(pair.value == 99)
+    }
+}
+
+@Test
+func mapRefKeyAndValueShareIdentity() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true, compatible: 
true))
+    fory.register(RefKeyNode.self, id: 9501)
+    fory.register(RefKeyValueHolder.self, id: 9503)
+
+    let shared = RefKeyNode(id: 11)
+    let value = RefKeyValueHolder(map: [shared: shared])
+
+    let decoded: RefKeyValueHolder = try fory.deserialize(try 
fory.serialize(value))
+    let pair = decoded.map.first
+
+    #expect(pair != nil)
+    if let pair {
+        #expect(pair.key === pair.value)
+    }
+}
+
+@Test
+func mapRefKeysChunkAcross255Entries() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true, compatible: 
true))
+    fory.register(RefKeyNode.self, id: 9501)
+    fory.register(RefKeyChunkHolder.self, id: 9504)
+
+    var keys: [RefKeyNode] = []
+    var map: [RefKeyNode: Int32] = [:]
+    for index in 0..<300 {
+        let key = RefKeyNode(id: Int32(index))
+        keys.append(key)
+        map[key] = Int32(index * 2)
+    }
+    let value = RefKeyChunkHolder(keys: keys, map: map)
+
+    let decoded: RefKeyChunkHolder = try fory.deserialize(try 
fory.serialize(value))
+    #expect(decoded.keys.count == 300)
+    #expect(decoded.map.count == 300)
+
+    var decodedMapKeysByID: [Int32: RefKeyNode] = [:]
+    for key in decoded.map.keys {
+        decodedMapKeysByID[key.id] = key
+    }
+
+    for key in decoded.keys {
+        #expect(decodedMapKeysByID[key.id] === key)
+        #expect(decoded.map[decodedMapKeysByID[key.id]!] == key.id * 2)
+    }
+}
+
+@Test
+func collectionSerializersRejectMalformedPrimitivePayloads() throws {
+    let int16Buffer = ByteBuffer()
+    int16Buffer.writeVarUInt32(3)
+    int16Buffer.writeBytes([0x01, 0x02, 0x03])
+    let int16Context = ReadContext(
+        buffer: int16Buffer,
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    do {
+        let _: [Int16] = try [Int16].foryReadData(int16Context)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("payload size mismatch"))
+    }
+
+    let float64Buffer = ByteBuffer()
+    float64Buffer.writeVarUInt32(4)
+    float64Buffer.writeBytes([0x01, 0x02, 0x03, 0x04])
+    let float64Context = ReadContext(
+        buffer: float64Buffer,
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    do {
+        let _: [Double] = try [Double].foryReadData(float64Context)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("payload size mismatch"))
+    }
+}
diff --git a/swift/Tests/ForyTests/CompatibilityTests.swift 
b/swift/Tests/ForyTests/CompatibilityTests.swift
new file mode 100644
index 000000000..49bf7b996
--- /dev/null
+++ b/swift/Tests/ForyTests/CompatibilityTests.swift
@@ -0,0 +1,294 @@
+// 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.
+
+import Foundation
+import Testing
+@testable import Fory
+
+@ForyObject
+private struct CompatibleProfileV1: Equatable {
+    var id: Int32 = 0
+    var name: String = ""
+}
+
+@ForyObject
+private struct CompatibleProfileV2: Equatable {
+    var id: Int32 = 0
+    var name: String = ""
+    var nickname: String = "guest"
+    var scores: [Int32] = []
+}
+
+@ForyObject
+private struct CompatibleNestedProfileV1: Equatable {
+    var id: Int32 = 0
+    var name: String = ""
+}
+
+@ForyObject
+private struct CompatibleNestedProfileV2: Equatable {
+    var id: Int32 = 0
+    var name: String = ""
+    var alias: String = ""
+    var scores: [Int32] = []
+}
+
+@ForyObject
+private struct CompatibleNestedArrayV1: Equatable {
+    var items: [CompatibleNestedProfileV1] = []
+}
+
+@ForyObject
+private struct CompatibleNestedArrayV2: Equatable {
+    var items: [CompatibleNestedProfileV2] = []
+}
+
+@ForyObject
+private struct CompatibleNestedMapV1: Equatable {
+    var items: [Int32: CompatibleNestedProfileV1] = [:]
+}
+
+@ForyObject
+private struct CompatibleNestedMapV2: Equatable {
+    var items: [Int32: CompatibleNestedProfileV2] = [:]
+}
+
+@ForyObject
+private struct SchemaVersionV1: Equatable {
+    var id: Int32 = 0
+    var name: String = ""
+}
+
+@ForyObject
+private struct SchemaVersionV2: Equatable {
+    var id: Int32 = 0
+    var alias: String = ""
+    var count: Int32 = 0
+}
+
+@ForyObject
+private final class CompatibleGraphNode {
+    var value: Int32 = 0
+    var next: CompatibleGraphNode?
+
+    required init() {}
+
+    init(value: Int32, next: CompatibleGraphNode? = nil) {
+        self.value = value
+        self.next = next
+    }
+}
+
+@ForyObject
+private final class CompatibleGraphContainer {
+    var first: CompatibleGraphNode?
+    var second: CompatibleGraphNode?
+    var items: [CompatibleGraphNode] = []
+    var byName: [String: CompatibleGraphNode] = [:]
+
+    required init() {}
+
+    init(
+        first: CompatibleGraphNode?,
+        second: CompatibleGraphNode?,
+        items: [CompatibleGraphNode],
+        byName: [String: CompatibleGraphNode]
+    ) {
+        self.first = first
+        self.second = second
+        self.items = items
+        self.byName = byName
+    }
+}
+
+@Test
+func compatibleModeSupportsAddedAndRemovedFields() throws {
+    let writerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV1.register(CompatibleProfileV1.self, id: 9901)
+
+    let readerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV2.register(CompatibleProfileV2.self, id: 9901)
+
+    let sourceV1 = CompatibleProfileV1(id: 7, name: "swift")
+    let bytesFromV1 = try writerV1.serialize(sourceV1)
+    let decodedAsV2: CompatibleProfileV2 = try 
readerV2.deserialize(bytesFromV1)
+    #expect(decodedAsV2.id == sourceV1.id)
+    #expect(decodedAsV2.name == sourceV1.name)
+    #expect(decodedAsV2.nickname == "")
+    #expect(decodedAsV2.scores.isEmpty)
+
+    let writerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV2.register(CompatibleProfileV2.self, id: 9901)
+
+    let readerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV1.register(CompatibleProfileV1.self, id: 9901)
+
+    let sourceV2 = CompatibleProfileV2(id: 9, name: "fory", nickname: "macro", 
scores: [1, 2, 3])
+    let bytesFromV2 = try writerV2.serialize(sourceV2)
+    let decodedAsV1: CompatibleProfileV1 = try 
readerV1.deserialize(bytesFromV2)
+    #expect(decodedAsV1 == CompatibleProfileV1(id: sourceV2.id, name: 
sourceV2.name))
+}
+
+@Test
+func schemaConsistentModeRejectsVersionHashMismatch() throws {
+    let writer = Fory(config: .init(xlang: true, trackRef: false, compatible: 
false, checkClassVersion: true))
+    writer.register(SchemaVersionV1.self, id: 9902)
+
+    let reader = Fory(config: .init(xlang: true, trackRef: false, compatible: 
false, checkClassVersion: true))
+    reader.register(SchemaVersionV2.self, id: 9902)
+
+    let bytes = try writer.serialize(SchemaVersionV1(id: 1, name: "shape"))
+    do {
+        let _: SchemaVersionV2 = try reader.deserialize(bytes)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("class version hash mismatch"))
+    }
+}
+
+@Test
+func compatibleModePreservesSharedAndCircularReferencesForMacroObjects() 
throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true, compatible: 
true))
+    fory.register(CompatibleGraphNode.self, id: 9903)
+    fory.register(CompatibleGraphContainer.self, id: 9904)
+
+    let shared = CompatibleGraphNode(value: 11)
+    shared.next = shared
+    let value = CompatibleGraphContainer(
+        first: shared,
+        second: shared,
+        items: [shared, shared],
+        byName: [
+            "left": shared,
+            "right": shared
+        ]
+    )
+
+    let decoded: CompatibleGraphContainer = try fory.deserialize(try 
fory.serialize(value))
+
+    #expect(decoded.first != nil)
+    #expect(decoded.first === decoded.second)
+    #expect(decoded.first === decoded.items[0])
+    #expect(decoded.items[0] === decoded.items[1])
+    #expect(decoded.byName["left"] === decoded.byName["right"])
+    #expect(decoded.byName["left"] === decoded.first)
+    #expect(decoded.first?.next === decoded.first)
+}
+
+@Test
+func compatibleNestedArrayEvolves() throws {
+    let writerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV1.register(CompatibleNestedProfileV1.self, id: 9910)
+    writerV1.register(CompatibleNestedArrayV1.self, id: 9911)
+
+    let readerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV2.register(CompatibleNestedProfileV2.self, id: 9910)
+    readerV2.register(CompatibleNestedArrayV2.self, id: 9911)
+
+    let sourceV1 = CompatibleNestedArrayV1(
+        items: [
+            CompatibleNestedProfileV1(id: 1, name: "alpha"),
+            CompatibleNestedProfileV1(id: 2, name: "beta")
+        ]
+    )
+    let decodedAsV2: CompatibleNestedArrayV2 = try readerV2.deserialize(try 
writerV1.serialize(sourceV1))
+    #expect(decodedAsV2.items.map(\.id) == [1, 2])
+    #expect(decodedAsV2.items.map(\.name) == ["alpha", "beta"])
+    #expect(decodedAsV2.items.allSatisfy { $0.alias.isEmpty })
+    #expect(decodedAsV2.items.allSatisfy { $0.scores.isEmpty })
+
+    let writerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV2.register(CompatibleNestedProfileV2.self, id: 9910)
+    writerV2.register(CompatibleNestedArrayV2.self, id: 9911)
+
+    let readerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV1.register(CompatibleNestedProfileV1.self, id: 9910)
+    readerV1.register(CompatibleNestedArrayV1.self, id: 9911)
+
+    let sourceV2 = CompatibleNestedArrayV2(
+        items: [
+            CompatibleNestedProfileV2(id: 3, name: "gamma", alias: "g", 
scores: [3, 4]),
+            CompatibleNestedProfileV2(id: 4, name: "delta", alias: "d", 
scores: [])
+        ]
+    )
+    let decodedAsV1: CompatibleNestedArrayV1 = try readerV1.deserialize(try 
writerV2.serialize(sourceV2))
+    #expect(decodedAsV1.items == [
+        CompatibleNestedProfileV1(id: 3, name: "gamma"),
+        CompatibleNestedProfileV1(id: 4, name: "delta")
+    ])
+}
+
+@Test
+func compatibleNestedMapEvolves() throws {
+    let writerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV1.register(CompatibleNestedProfileV1.self, id: 9910)
+    writerV1.register(CompatibleNestedMapV1.self, id: 9912)
+
+    let readerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV2.register(CompatibleNestedProfileV2.self, id: 9910)
+    readerV2.register(CompatibleNestedMapV2.self, id: 9912)
+
+    let sourceV1 = CompatibleNestedMapV1(
+        items: [
+            1: CompatibleNestedProfileV1(id: 10, name: "first"),
+            2: CompatibleNestedProfileV1(id: 20, name: "second")
+        ]
+    )
+    let decodedAsV2: CompatibleNestedMapV2 = try readerV2.deserialize(try 
writerV1.serialize(sourceV1))
+    #expect(decodedAsV2.items[1]?.id == 10)
+    #expect(decodedAsV2.items[1]?.name == "first")
+    #expect(decodedAsV2.items[1]?.alias == "")
+    #expect(decodedAsV2.items[1]?.scores.isEmpty == true)
+    #expect(decodedAsV2.items[2]?.id == 20)
+    #expect(decodedAsV2.items[2]?.name == "second")
+    #expect(decodedAsV2.items[2]?.alias == "")
+    #expect(decodedAsV2.items[2]?.scores.isEmpty == true)
+}
+
+@Test
+func compatibleNestedReadsReuseTypeMeta() throws {
+    let writerV1 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    writerV1.register(CompatibleNestedProfileV1.self, id: 9910)
+    writerV1.register(CompatibleNestedArrayV1.self, id: 9911)
+
+    let readerV2 = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    readerV2.register(CompatibleNestedProfileV2.self, id: 9910)
+    readerV2.register(CompatibleNestedArrayV2.self, id: 9911)
+
+    let first = CompatibleNestedArrayV1(
+        items: [
+            CompatibleNestedProfileV1(id: 1, name: "alpha"),
+            CompatibleNestedProfileV1(id: 2, name: "beta")
+        ]
+    )
+    let second = CompatibleNestedArrayV1(
+        items: [
+            CompatibleNestedProfileV1(id: 3, name: "gamma"),
+            CompatibleNestedProfileV1(id: 4, name: "delta"),
+            CompatibleNestedProfileV1(id: 5, name: "epsilon")
+        ]
+    )
+
+    let decodedFirst: CompatibleNestedArrayV2 = try readerV2.deserialize(try 
writerV1.serialize(first))
+    let decodedSecond: CompatibleNestedArrayV2 = try readerV2.deserialize(try 
writerV1.serialize(second))
+
+    #expect(decodedFirst.items.map(\.id) == [1, 2])
+    #expect(decodedSecond.items.map(\.id) == [3, 4, 5])
+    #expect(decodedSecond.items.map(\.name) == ["gamma", "delta", "epsilon"])
+    #expect(decodedSecond.items.allSatisfy { $0.alias.isEmpty })
+    #expect(decodedSecond.items.allSatisfy { $0.scores.isEmpty })
+}
diff --git a/swift/Tests/ForyTests/ForySwiftTests.swift 
b/swift/Tests/ForyTests/ForySwiftTests.swift
index 146138d86..60cf23164 100644
--- a/swift/Tests/ForyTests/ForySwiftTests.swift
+++ b/swift/Tests/ForyTests/ForySwiftTests.swift
@@ -251,6 +251,66 @@ func extendedWireTypesRoundTrip() throws {
     #expect(float16ArrayDecoded.map(\.bitPattern) == 
float16Array.map(\.bitPattern))
 }
 
+@Test
+func floatingSpecialsRoundTrip() throws {
+    let fory = Fory()
+
+    let floatValues: [Float] = [
+        0.0,
+        -0.0,
+        .infinity,
+        -.infinity,
+        .leastNonzeroMagnitude,
+        .greatestFiniteMagnitude,
+        Float(bitPattern: 0x7FC0_1234)
+    ]
+    for value in floatValues {
+        let decoded: Float = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded.bitPattern == value.bitPattern)
+    }
+
+    let doubleValues: [Double] = [
+        0.0,
+        -0.0,
+        .infinity,
+        -.infinity,
+        .leastNonzeroMagnitude,
+        .greatestFiniteMagnitude,
+        Double(bitPattern: 0x7FF8_0000_0000_1234)
+    ]
+    for value in doubleValues {
+        let decoded: Double = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded.bitPattern == value.bitPattern)
+    }
+
+    let float16Values: [Float16] = [
+        .init(bitPattern: 0x0000),
+        .init(bitPattern: 0x8000),
+        .init(bitPattern: 0x7C00),
+        .init(bitPattern: 0xFC00),
+        .init(bitPattern: 0x0001),
+        .init(bitPattern: 0x7BFF),
+        .init(bitPattern: 0x7E11)
+    ]
+    for value in float16Values {
+        let decoded: Float16 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded.bitPattern == value.bitPattern)
+    }
+
+    let bfloat16Values: [BFloat16] = [
+        .init(rawValue: 0x0000),
+        .init(rawValue: 0x8000),
+        .init(rawValue: 0x7F80),
+        .init(rawValue: 0xFF80),
+        .init(rawValue: 0x0001),
+        .init(rawValue: 0x7FC1)
+    ]
+    for value in bfloat16Values {
+        let decoded: BFloat16 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded.rawValue == value.rawValue)
+    }
+}
+
 @Test
 func namedInitializerBuildsConfig() {
     let defaultConfig = Fory()
@@ -518,6 +578,59 @@ func registrationIsRejectedAfterFirstTopLevelUse() throws {
     }
 }
 
+@Test
+func serializeToAppendsRoots() throws {
+    let fory = Fory()
+    let first = Int32(7)
+    let second = "swift-buffer"
+    let third: String? = nil
+
+    let firstData = try fory.serialize(first)
+    let secondData = try fory.serialize(second)
+    let thirdData = try fory.serialize(third)
+
+    var stream = Data()
+    try fory.serialize(first, to: &stream)
+    try fory.serialize(second, to: &stream)
+    try fory.serialize(third, to: &stream)
+
+    var expected = Data()
+    expected.append(firstData)
+    expected.append(secondData)
+    expected.append(thirdData)
+    #expect(stream == expected)
+
+    let buffer = ByteBuffer(data: stream)
+    let decodedFirst: Int32 = try fory.deserialize(from: buffer)
+    #expect(decodedFirst == first)
+    #expect(buffer.getCursor() == firstData.count)
+
+    let decodedSecond: String = try fory.deserialize(from: buffer)
+    #expect(decodedSecond == second)
+    #expect(buffer.getCursor() == firstData.count + secondData.count)
+
+    let decodedThird: String? = try fory.deserialize(from: buffer)
+    #expect(decodedThird == nil)
+    #expect(buffer.remaining == 0)
+}
+
+@Test
+func rootBufferHonorsCursor() throws {
+    let fory = Fory()
+    let prefix: [UInt8] = [0xAA, 0xBB, 0xCC]
+    let payload = try fory.serialize("offset")
+
+    let buffer = ByteBuffer()
+    buffer.writeBytes(prefix)
+    buffer.writeBytes(Array(payload))
+    buffer.setCursor(prefix.count)
+
+    let decoded: String = try fory.deserialize(from: buffer)
+    #expect(decoded == "offset")
+    #expect(buffer.getCursor() == buffer.count)
+    #expect(Array(buffer.storage.prefix(prefix.count)) == prefix)
+}
+
 @Test
 func topLevelAnyObjectRoundTrip() throws {
     let fory = Fory(config: .init(xlang: true, trackRef: true))
@@ -596,6 +709,31 @@ func 
macroDynamicAnyObjectAndAnySerializerFieldsRoundTrip() throws {
     #expect(serializerDecoded.map["address"] as? Address == Address(street: 
"Mapped", zip: 10003))
 }
 
+@Test
+func dynamicAnySerializerTracksRefs() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: true))
+    fory.register(Node.self, id: 226)
+    fory.register(AnySerializerHolder.self, id: 227)
+
+    let shared = Node(value: 88)
+    shared.next = shared
+    let value = AnySerializerHolder(
+        value: shared,
+        items: [shared],
+        map: ["shared": shared]
+    )
+
+    let decoded: AnySerializerHolder = try fory.deserialize(try 
fory.serialize(value))
+    let root = decoded.value as? Node
+    let item = decoded.items.first as? Node
+    let mapped = decoded.map["shared"] as? Node
+
+    #expect(root != nil)
+    #expect(root === item)
+    #expect(item === mapped)
+    #expect(root?.next === root)
+}
+
 @Test
 func macroAnyFieldsRoundTrip() throws {
     let fory = Fory()
diff --git a/swift/Tests/ForyTests/StringSerializerTests.swift 
b/swift/Tests/ForyTests/StringSerializerTests.swift
new file mode 100644
index 000000000..a0e0cef0d
--- /dev/null
+++ b/swift/Tests/ForyTests/StringSerializerTests.swift
@@ -0,0 +1,147 @@
+// 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.
+
+import Foundation
+import Testing
+@testable import Fory
+
+private enum ManualStringEncoding: UInt64 {
+    case latin1 = 0
+    case utf16 = 1
+    case utf8 = 2
+}
+
+private func utf16LittleEndianBytes(_ value: String) -> [UInt8] {
+    value.utf16.flatMap { unit in
+        [
+            UInt8(truncatingIfNeeded: unit),
+            UInt8(truncatingIfNeeded: unit >> 8)
+        ]
+    }
+}
+
+private func makeStringReadContext(payload: [UInt8], encoding: 
ManualStringEncoding) -> ReadContext {
+    let buffer = ByteBuffer()
+    buffer.writeVarUInt36Small((UInt64(payload.count) << 2) | 
encoding.rawValue)
+    buffer.writeBytes(payload)
+    return ReadContext(
+        buffer: buffer,
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+}
+
+private func stringPayloadBytes(for value: String) throws -> [UInt8] {
+    let context = WriteContext(
+        buffer: ByteBuffer(),
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    try value.foryWriteData(context, hasGenerics: false)
+    return Array(context.buffer.storage.prefix(context.buffer.count))
+}
+
+@Test
+func stringSerializerRoundTripsUnicodeAndLengthBoundaries() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: false, compatible: 
true))
+    let values = [
+        "",
+        "ascii",
+        String(repeating: "a", count: 200),
+        "café",
+        "你好,Swift",
+        "emoji 👩🏽‍💻🚀",
+        "e\u{301}",
+        "null\u{0000}byte",
+        "𐍈 Gothic"
+    ]
+
+    for value in values {
+        let data = try fory.serialize(value)
+        let decoded: String = try fory.deserialize(data)
+        #expect(decoded == value)
+
+        let payload = try stringPayloadBytes(for: value)
+        let headerReader = ByteBuffer(bytes: payload)
+        let header = try headerReader.readVarUInt36Small()
+        #expect((header & 0x03) == ManualStringEncoding.utf8.rawValue)
+        #expect(Int(header >> 2) == value.utf8.count)
+
+        let context = ReadContext(
+            buffer: ByteBuffer(bytes: payload),
+            typeResolver: TypeResolver(trackRef: false),
+            trackRef: false
+        )
+        #expect(try String.foryReadData(context) == value)
+    }
+}
+
+@Test
+func stringSerializerReadsUtf8Latin1AndUtf16Payloads() throws {
+    let latin1Context = makeStringReadContext(
+        payload: [0x63, 0x61, 0x66, 0xE9],
+        encoding: .latin1
+    )
+    #expect(try String.foryReadData(latin1Context) == "café")
+
+    let utf16Value = "你好😀"
+    let utf16Context = makeStringReadContext(
+        payload: utf16LittleEndianBytes(utf16Value),
+        encoding: .utf16
+    )
+    #expect(try String.foryReadData(utf16Context) == utf16Value)
+
+    let utf8Value = "emoji 👩🏽‍💻"
+    let utf8Context = makeStringReadContext(
+        payload: Array(utf8Value.utf8),
+        encoding: .utf8
+    )
+    #expect(try String.foryReadData(utf8Context) == utf8Value)
+}
+
+@Test
+func stringSerializerRejectsInvalidPayloads() throws {
+    let oddUTF16 = makeStringReadContext(payload: [0x41], encoding: .utf16)
+    do {
+        _ = try String.foryReadData(oddUTF16)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("utf16 byte length is not even"))
+    }
+
+    let invalidUTF8 = makeStringReadContext(payload: [0xC3, 0x28], encoding: 
.utf8)
+    do {
+        _ = try String.foryReadData(invalidUTF8)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("invalid UTF-8"))
+    }
+
+    let unsupportedEncodingBuffer = ByteBuffer()
+    unsupportedEncodingBuffer.writeVarUInt36Small(3)
+    let unsupportedEncoding = ReadContext(
+        buffer: unsupportedEncodingBuffer,
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    do {
+        _ = try String.foryReadData(unsupportedEncoding)
+        #expect(Bool(false))
+    } catch {
+        #expect("\(error)".contains("unsupported string encoding"))
+    }
+}
diff --git a/swift/Tests/ForyTests/UnsignedTests.swift 
b/swift/Tests/ForyTests/UnsignedTests.swift
new file mode 100644
index 000000000..f90020579
--- /dev/null
+++ b/swift/Tests/ForyTests/UnsignedTests.swift
@@ -0,0 +1,188 @@
+// 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.
+
+import Foundation
+import Testing
+@testable import Fory
+
+@ForyObject
+private struct UnsignedFieldBundle: Equatable {
+    var u8: UInt8 = 0
+    var u16: UInt16 = 0
+    var u32Var: UInt32 = 0
+    var u32Fixed: ForyUInt32Fixed = .init()
+    var u64Var: UInt64 = 0
+    var u64Fixed: ForyUInt64Fixed = .init()
+    var u64Tagged: ForyUInt64Tagged = .init()
+
+    var u8Nullable: UInt8?
+    var u16Nullable: UInt16?
+    var u32VarNullable: UInt32?
+    var u32FixedNullable: ForyUInt32Fixed?
+    var u64VarNullable: UInt64?
+    var u64FixedNullable: ForyUInt64Fixed?
+    var u64TaggedNullable: ForyUInt64Tagged?
+}
+
+@Test
+func unsignedPrimitiveRoundTripsCoverZeroMidpointAndMax() throws {
+    let fory = Fory()
+
+    let uint8Values: [UInt8] = [0, 127, 128, UInt8.max]
+    let uint16Values: [UInt16] = [0, 32_767, 32_768, UInt16.max]
+    let uint32Values: [UInt32] = [0, UInt32(Int32.max), UInt32(Int32.max) + 1, 
UInt32.max]
+    let uint64Values: [UInt64] = [0, UInt64(Int64.max), UInt64(Int64.max) + 1, 
UInt64.max]
+
+    for value in uint8Values {
+        let decoded: UInt8 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded == value)
+    }
+    for value in uint16Values {
+        let decoded: UInt16 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded == value)
+    }
+    for value in uint32Values {
+        let decoded: UInt32 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded == value)
+    }
+    for value in uint64Values {
+        let decoded: UInt64 = try fory.deserialize(try fory.serialize(value))
+        #expect(decoded == value)
+    }
+}
+
+@Test
+func unsignedEncodingWrappersPreserveExpectedWireWidths() throws {
+    let fixed32 = ForyUInt32Fixed(rawValue: UInt32.max)
+    let fixed32Context = WriteContext(
+        buffer: ByteBuffer(),
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    try fixed32.foryWriteData(fixed32Context, hasGenerics: false)
+    #expect(fixed32Context.buffer.count == 4)
+    let fixed32Decoded = try ForyUInt32Fixed.foryReadData(
+        ReadContext(buffer: fixed32Context.buffer, typeResolver: 
TypeResolver(trackRef: false), trackRef: false)
+    )
+    #expect(fixed32Decoded == fixed32)
+
+    let fixed64 = ForyUInt64Fixed(rawValue: UInt64.max)
+    let fixed64Context = WriteContext(
+        buffer: ByteBuffer(),
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    try fixed64.foryWriteData(fixed64Context, hasGenerics: false)
+    #expect(fixed64Context.buffer.count == 8)
+    let fixed64Decoded = try ForyUInt64Fixed.foryReadData(
+        ReadContext(buffer: fixed64Context.buffer, typeResolver: 
TypeResolver(trackRef: false), trackRef: false)
+    )
+    #expect(fixed64Decoded == fixed64)
+
+    let compactTagged = ForyUInt64Tagged(rawValue: UInt64(Int32.max))
+    let compactContext = WriteContext(
+        buffer: ByteBuffer(),
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    try compactTagged.foryWriteData(compactContext, hasGenerics: false)
+    #expect(compactContext.buffer.count == 4)
+    let compactDecoded = try ForyUInt64Tagged.foryReadData(
+        ReadContext(buffer: compactContext.buffer, typeResolver: 
TypeResolver(trackRef: false), trackRef: false)
+    )
+    #expect(compactDecoded == compactTagged)
+
+    let wideTagged = ForyUInt64Tagged(rawValue: UInt64(Int32.max) + 1)
+    let wideContext = WriteContext(
+        buffer: ByteBuffer(),
+        typeResolver: TypeResolver(trackRef: false),
+        trackRef: false
+    )
+    try wideTagged.foryWriteData(wideContext, hasGenerics: false)
+    #expect(wideContext.buffer.count == 9)
+    let wideDecoded = try ForyUInt64Tagged.foryReadData(
+        ReadContext(buffer: wideContext.buffer, typeResolver: 
TypeResolver(trackRef: false), trackRef: false)
+    )
+    #expect(wideDecoded == wideTagged)
+}
+
+@Test
+func unsignedMacroFieldsRoundTripAcrossSchemaModes() throws {
+    let cases = [
+        UnsignedFieldBundle(
+            u8: 0,
+            u16: 0,
+            u32Var: 0,
+            u32Fixed: .init(rawValue: 0),
+            u64Var: 0,
+            u64Fixed: .init(rawValue: 0),
+            u64Tagged: .init(rawValue: 0),
+            u8Nullable: nil,
+            u16Nullable: nil,
+            u32VarNullable: nil,
+            u32FixedNullable: nil,
+            u64VarNullable: nil,
+            u64FixedNullable: nil,
+            u64TaggedNullable: nil
+        ),
+        UnsignedFieldBundle(
+            u8: 128,
+            u16: 32_768,
+            u32Var: UInt32(Int32.max) + 1,
+            u32Fixed: .init(rawValue: UInt32(Int32.max) + 1),
+            u64Var: UInt64(Int64.max) + 1,
+            u64Fixed: .init(rawValue: UInt64(Int64.max) + 1),
+            u64Tagged: .init(rawValue: UInt64(Int32.max) + 1),
+            u8Nullable: 128,
+            u16Nullable: 32_768,
+            u32VarNullable: UInt32(Int32.max) + 1,
+            u32FixedNullable: .init(rawValue: UInt32(Int32.max) + 1),
+            u64VarNullable: UInt64(Int64.max) + 1,
+            u64FixedNullable: .init(rawValue: UInt64(Int64.max) + 1),
+            u64TaggedNullable: .init(rawValue: UInt64(Int32.max) + 1)
+        ),
+        UnsignedFieldBundle(
+            u8: UInt8.max,
+            u16: UInt16.max,
+            u32Var: UInt32.max,
+            u32Fixed: .init(rawValue: UInt32.max),
+            u64Var: UInt64.max,
+            u64Fixed: .init(rawValue: UInt64.max),
+            u64Tagged: .init(rawValue: UInt64.max),
+            u8Nullable: UInt8.max,
+            u16Nullable: UInt16.max,
+            u32VarNullable: UInt32.max,
+            u32FixedNullable: .init(rawValue: UInt32.max),
+            u64VarNullable: UInt64.max,
+            u64FixedNullable: .init(rawValue: UInt64.max),
+            u64TaggedNullable: .init(rawValue: UInt64.max)
+        )
+    ]
+
+    let schemaConsistent = Fory(config: .init(xlang: true, trackRef: false, 
compatible: false))
+    schemaConsistent.register(UnsignedFieldBundle.self, id: 9801)
+
+    let compatible = Fory(config: .init(xlang: true, trackRef: false, 
compatible: true))
+    compatible.register(UnsignedFieldBundle.self, id: 9801)
+
+    for value in cases {
+        let decodedSchema: UnsignedFieldBundle = try 
schemaConsistent.deserialize(try schemaConsistent.serialize(value))
+        let decodedCompatible: UnsignedFieldBundle = try 
compatible.deserialize(try compatible.serialize(value))
+        #expect(decodedSchema == value)
+        #expect(decodedCompatible == value)
+    }
+}


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


Reply via email to