Skip to main content

Serialization

IRestSerializer

All serialization goes through IRestSerializer:

public interface IRestSerializer
{
string ContentType { get; }

[RequiresDynamicCode("...")]
[RequiresUnreferencedCode("...")]
ValueTask<T?> DeserializeAsync<T>(Stream stream, CancellationToken ct = default);

[RequiresDynamicCode("...")]
[RequiresUnreferencedCode("...")]
ValueTask SerializeAsync<T>(Stream stream, T value, CancellationToken ct = default);
}

The ContentType property controls both the Content-Type header on requests and the Accept header.

Built-in serializers

System.Text.Json

dotnet add package ZeroAlloc.Rest.SystemTextJson
services.AddIUserApi(options =>
{
options.BaseAddress = new Uri("https://api.example.com");
options.UseSerializer<SystemTextJsonSerializer>();
});

Uses JsonSerializerDefaults.Web (camelCase, case-insensitive). Content-Type: application/json.

MemoryPack

dotnet add package ZeroAlloc.Rest.MemoryPack
options.UseSerializer<MemoryPackSerializer>();

Content-Type: application/x-memorypack. Both endpoints must understand MemoryPack encoding.

MessagePack

dotnet add package ZeroAlloc.Rest.MessagePack
options.UseSerializer<MessagePackSerializer>();

Content-Type: application/x-msgpack.

Custom serializer

Implement IRestSerializer:

public sealed class MySerializer : IRestSerializer
{
public string ContentType => "application/json";

[RequiresDynamicCode("Serialization may require dynamic code.")]
[RequiresUnreferencedCode("Serialization may require unreferenced code.")]
public async ValueTask<T?> DeserializeAsync<T>(Stream stream, CancellationToken ct = default)
{
return await JsonSerializer.DeserializeAsync<T>(stream, cancellationToken: ct);
}

[RequiresDynamicCode("Serialization may require dynamic code.")]
[RequiresUnreferencedCode("Serialization may require unreferenced code.")]
public async ValueTask SerializeAsync<T>(Stream stream, T value, CancellationToken ct = default)
{
await JsonSerializer.SerializeAsync(stream, value, cancellationToken: ct);
}
}

Register it:

options.UseSerializer<MySerializer>();

Per-method serializer override

When one endpoint speaks a different protocol, annotate that specific method:

[ZeroAllocRestClient]
public interface IUploadApi
{
[Post("/upload")]
[Serializer(typeof(MemoryPackSerializer))]
Task UploadAsync([Body] byte[] data, CancellationToken ct = default);

[Get("/status")]
Task<StatusDto> GetStatusAsync(CancellationToken ct = default); // uses default serializer
}

The generator injects MemoryPackSerializer as a constructor parameter and uses it only for UploadAsync. The DI emitter also registers TryAddSingleton<MemoryPackSerializer> automatically.