Basic Serialization
This page covers typed serialization APIs in Apache Fory™ C#.
Object Graph Serialization
Use [ForyStruct] on your classes/structs and register them before use.
using Apache.Fory;
[ForyStruct]
public sealed class Address
{
public string Street { get; set; } = string.Empty;
public int Zip { get; set; }
}
[ForyStruct]
public sealed class Person
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Nickname { get; set; }
public List<int> Scores { get; set; } = [];
public List<Address> Addresses { get; set; } = [];
}
Fory fory = Fory.Builder().Build();
fory.Register<Address>(100);
fory.Register<Person>(101);
Person person = new()
{
Id = 42,
Name = "Alice",
Nickname = null,
Scores = [10, 20, 30],
Addresses = [new Address { Street = "Main", Zip = 94107 }],
};
byte[] payload = fory.Serialize(person);
Person decoded = fory.Deserialize<Person>(payload);
Class Inheritance
An annotated class includes the supported members declared by its annotated
base classes in one flattened schema. Annotate every class in the hierarchy
directly; [ForyStruct] is not inherited.
[ForyStruct]
public abstract class Entity
{
[ForyField(1)]
private long _id;
public long Id => _id;
}
[ForyStruct]
public sealed class User : Entity
{
[ForyField(2)]
public string Name { get; set; } = string.Empty;
}
Public and assembly-accessible mutable members are included automatically.
Private, protected-only, and otherwise inaccessible fields or properties must
carry [ForyField] on the class that declares them. Abstract annotated bases
publish schema information for concrete descendants but are not registered or
serialized as roots.
The concrete derived type is still registered once:
fory.Register<User>(102);
For a base class from an unmodifiable package, declare its fields explicitly as described in External Types.
Typed API
Serialize / Deserialize with byte arrays
byte[] payload = fory.Serialize(value);
MyType decoded = fory.Deserialize<MyType>(payload);
Deserialize from ReadOnlySpan<byte>
ReadOnlySpan<byte> span = payload;
MyType decoded = fory.Deserialize<MyType>(span);
Stream-style frame consumption
using System.Buffers;
ReadOnlySequence<byte> sequence = GetFramedSequence();
MyType first = fory.Deserialize<MyType>(ref sequence);
MyType second = fory.Deserialize<MyType>(ref sequence);
Dynamic Payloads via Generic Object API
When the compile-time type is unknown or heterogeneous, use the generic API with object?.
Dictionary<object, object?> value = new()
{
["k1"] = 7,
[2] = "v2",
[true] = null,
};
byte[] payload = fory.Serialize<object?>(value);
object? decoded = fory.Deserialize<object?>(payload);
Dynamic maps normally decode as Dictionary<object, object?> when they have no
null key. If the payload uses reference tracking for the dynamic map itself, C#
returns NullableKeyDictionary<object, object?> so nested references and null
keys point to the decoded map owner.
Buffer Writer API
Serialize directly into IBufferWriter<byte> targets.
using System.Buffers;
ArrayBufferWriter<byte> writer = new();
fory.Serialize(writer, value);
ArrayBufferWriter<byte> dynamicWriter = new();
fory.Serialize<object?>(dynamicWriter, value);
Notes
- Reuse the same
ForyorThreadSafeForyinstance for better performance. - Primitive types and collections do not require user registration.
- Register user types handled by
[ForyStruct],[ForyEnum],[ForyUnion], external structural serializers, or custom serializers explicitly.