Skip to content

Custom Formatter

Applications can provide custom formatter implementations when the built-in JSON formatters are not sufficient.

A custom formatter is useful when you need:

  • a non-JSON payload format;
  • compression;
  • encryption;
  • custom binary serialization;
  • compatibility with an existing protocol.

Implement IDataFormatter

A custom formatter must implement IDataFormatter.

Conceptually, a formatter converts:

CLR object -> byte[]
byte[] -> CLR object

Example

public sealed class CustomDataFormatter : IDataFormatter
{
    public Encoding Encoder { get; } = Encoding.UTF8;

    public byte[] Encode<TData>(TData data)
    {
        var json = JsonSerializer.Serialize(data);

        return Encoder.GetBytes(json);
    }

    public TData Decode<TData>(byte[] data)
    {
        var json = Encoder.GetString(data);

        return JsonSerializer.Deserialize<TData>(json)!;
    }
}

Compression Example

A custom formatter can wrap another formatter.

public sealed class CompressedDataFormatter(
    IDataFormatter innerFormatter)
    : IDataFormatter
{
    public Encoding Encoder => innerFormatter.Encoder;

    public byte[] Encode<TData>(TData data)
    {
        var payload = innerFormatter.Encode(data);

        return Compress(payload);
    }

    public TData Decode<TData>(byte[] data)
    {
        var payload = Decompress(data);

        return innerFormatter.Decode<TData>(payload);
    }

    private static byte[] Compress(byte[] data)
    {
        // Apply compression.

        return data;
    }

    private static byte[] Decompress(byte[] data)
    {
        // Apply decompression.

        return data;
    }
}

Registration

Register the custom formatter wherever publishers and subscribers are created.

IDataFormatter formatter =
    new CustomDataFormatter();

For dependency injection scenarios, register it as the IDataFormatter implementation used by the application.


Compatibility

Both sender and receiver must use compatible payload formats.

graph LR
    Publisher["Publisher<br/>Custom Formatter"]
    Payload["byte[]"]
    Subscriber["Subscriber<br/>Compatible Formatter"]

    Publisher --> Payload
    Payload --> Subscriber

If the receiver cannot decode the payload, message processing will fail.


Best Practices

  • Keep the payload format stable.
  • Version the payload format when necessary.
  • Avoid changing serialization rules without migration planning.
  • Keep formatter exceptions clear and actionable.
  • Test custom formatters with real message contracts.