Serialization | Foundatio

All Foundatio implementations use serialization for storing and transmitting data. Understanding serialization options helps you optimize performance and choose the right format for your needs.

ISerializer Interface

Foundatio defines a simple serialization interface that all implementations use:

View source

csharp

/// <summary>
/// Defines methods for serializing and deserializing objects to and from streams.
/// </summary>
public interface ISerializer
{
    /// <summary>
    /// Deserializes data from a stream into an object of the specified type.
    /// </summary>
    object Deserialize(Stream data, Type objectType);

    /// <summary>
    /// Serializes an object to the specified output stream.
    /// Null values are valid and will be serialized (e.g., as "null" for JSON serializers
    /// or nil markers for binary serializers).
    /// </summary>
    void Serialize(object value, Stream output);
}

/// <summary>
/// Marker interface for serializers that produce human-readable text output (e.g., JSON, XML).
/// Text serializers use UTF-8 encoding for string conversions in extension methods.
/// </summary>
public interface ITextSerializer : ISerializer { }

This abstraction allows you to swap serializers without changing your code.

Extension Methods

The SerializerExtensions class provides convenient methods for common serialization scenarios:

csharp

// Deserialize from various sources
T Deserialize<T>(this ISerializer serializer, Stream data)
T Deserialize<T>(this ISerializer serializer, byte[] data)
T Deserialize<T>(this ISerializer serializer, string data)
object Deserialize(this ISerializer serializer, byte[] data, Type objectType)
object Deserialize(this ISerializer serializer, string data, Type objectType)

// Serialize to various formats
byte[] SerializeToBytes<T>(this ISerializer serializer, T value)
string SerializeToString<T>(this ISerializer serializer, T value)

Input Validation:

  • Deserialization methods validate inputs and throw exceptions:

    • ArgumentNullException - when serializer, stream, or byte array is null
    • ArgumentException - when byte array is empty, or string is null, empty, or whitespace
  • Serialization methods (SerializeToBytes, SerializeToString):

    • Throw ArgumentNullException if serializer is null
    • Null values are serialized to valid output (e.g., "null" for JSON, nil marker for MessagePack)

Text vs Binary Serializers:

When using string-based methods:

  • Text serializers (ITextSerializer): Strings are treated as UTF-8 encoded text
  • Binary serializers: Strings are treated as Base64-encoded binary data

Default Serializer

Foundatio uses SystemTextJsonSerializer (System.Text.Json) by default:

csharp

// Default - uses System.Text.Json
var cache = new InMemoryCacheClient();

// Equivalent to:
var cache = new InMemoryCacheClient(o =>
    o.Serializer = new SystemTextJsonSerializer());

Why System.Text.Json?

  • Built into .NET (no extra dependencies)
  • Fast and efficient
  • Human-readable JSON format
  • Good balance of performance and debuggability

Custom JSON Options

Configure System.Text.Json serialization behavior:

csharp

var jsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = false,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    Converters = { new JsonStringEnumConverter() }
};

var serializer = new SystemTextJsonSerializer(jsonOptions);

var cache = new InMemoryCacheClient(o => o.Serializer = serializer);
var queue = new InMemoryQueue<WorkItem>(o => o.Serializer = serializer);
var messageBus = new InMemoryMessageBus(o => o.Serializer = serializer);

Global Default Serializer

Set the default serializer for all new instances that don't explicitly specify one:

csharp

// Set globally (affects all new instances that don't specify a serializer)
DefaultSerializer.Instance = new SystemTextJsonSerializer(myJsonOptions);

// Now all new instances use your custom serializer
var cache = new InMemoryCacheClient(); // Uses your custom serializer
var queue = new InMemoryQueue<WorkItem>(); // Uses your custom serializer

How it works:

  • When you don't specify a serializer, SharedOptions.Serializer falls back to DefaultSerializer.Instance
  • This allows you to configure serialization once for your entire application
  • Useful for setting up camelCase naming, custom converters, or other JSON options globally

Available Serializers

Foundatio provides several serializer implementations via NuGet packages:

System.Text.Json (Default)

bash

# Included in Foundatio package (no extra install needed)
dotnet add package Foundatio

View source

csharp

var serializer = new SystemTextJsonSerializer(jsonOptions);

When to use:

  • Default choice for most applications
  • Good performance and .NET native support
  • Human-readable JSON for debugging
  • Well-supported by .NET ecosystem

Newtonsoft.Json (Json.NET)

bash

dotnet add package Foundatio.JsonNet

csharp

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var serializer = new JsonNetSerializer(settings);

When to use:

  • Need $type handling for polymorphic types
  • Existing codebase uses Newtonsoft.Json extensively
  • Require specific Newtonsoft.Json features not in System.Text.Json
  • Legacy compatibility

MessagePack (Binary)

bash

dotnet add package Foundatio.MessagePack

csharp

var options = MessagePackSerializerOptions.Standard
    .WithResolver(ContractlessStandardResolver.Instance);
var serializer = new MessagePackSerializer(options);

When to use:

  • High-throughput scenarios where size and speed are critical
  • Queue messages, cache values in high-volume systems
  • Network bandwidth is limited
  • Binary format is acceptable (not human-readable)

Performance: ~2-5x faster than JSON, 50-70% smaller payloads

Performance Comparison

SerializerSpeedSizeHuman ReadableType InfoDependencies
System.Text.JsonFastMediumBuilt-in
MessagePackVery FastSmallOptionalMessagePack NuGet
Newtonsoft.JsonMediumMediumNewtonsoft.Json NuGet

Choosing the Right Serializer

Use System.Text.Json (Default) When:

  • Starting a new project
  • You want good balance of speed, size, and debuggability
  • You don't need advanced features like $type handling
  • You prefer built-in .NET support

Use MessagePack When:

  • Processing high message volumes (>10k messages/sec)
  • Network bandwidth or storage size is constrained
  • Speed is more important than human-readability
  • You have control over both producer and consumer

csharp

// High-throughput queue example
var serializer = new MessagePackSerializer();
var queue = new RedisQueue<HighVolumeEvent>(o =>
{
    o.Serializer = serializer;
    o.ConnectionString = connectionString;
});

Use Newtonsoft.Json When:

  • You need $type handling for polymorphic serialization
  • Migrating from legacy code that uses Json.NET
  • Require specific Json.NET features (custom converters, complex scenarios)

csharp

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto
};
var serializer = new JsonNetSerializer(settings);
var cache = new RedisCacheClient(o => o.Serializer = serializer);

Using Serializers with Foundatio

All Foundatio implementations accept a serializer via their options:

csharp

var serializer = new MessagePackSerializer();

// Caching
var cache = new InMemoryCacheClient(o => o.Serializer = serializer);

// Queues
var queue = new InMemoryQueue<WorkItem>(o => o.Serializer = serializer);

// Messaging
var messageBus = new InMemoryMessageBus(o => o.Serializer = serializer);

// Storage (for metadata serialization)
var storage = new InMemoryFileStorage(o => o.Serializer = serializer);

For DI configuration and shared options across implementations, see Dependency Injection.

Serialization Exceptions

Foundatio provides SerializerException as a base exception type for serialization-specific error conditions. Built-in serializers (System.Text.Json, Newtonsoft.Json, MessagePack) throw their native exceptions (e.g., JsonException, MessagePackSerializationException). SerializerException is primarily used for:

  • Testing: The FaultInjectingSerializer in Foundatio.TestHarness throws SerializerException to simulate serialization failures in unit and integration tests
  • Custom serializers: Wrap implementation-specific exceptions when you want a unified exception type across your application

Component-Specific Exception Types

Foundatio provides dedicated exception types for each infrastructure component:

ComponentException TypeNamespace
CachingCacheExceptionFoundatio.Caching
QueuesQueueExceptionFoundatio.Queues
MessagingMessageBusExceptionFoundatio.Messaging
StorageStorageExceptionFoundatio.Storage
SerializationSerializerExceptionFoundatio.Serializer
ResilienceBrokenCircuitExceptionFoundatio.Resilience

These ensure consumers get predictable exception types regardless of the underlying implementation (Redis, Azure, AWS, etc.).

Serialization Considerations

Binary vs Text Serializers

Text Serializers (JSON):

  • Implement ITextSerializer marker interface
  • Human-readable (can debug with text tools)
  • Slightly larger payloads
  • Compatible across different systems/languages
  • Extension methods use UTF-8 encoding for string conversions

Binary Serializers (MessagePack):

  • Implement ISerializer only (not ITextSerializer)
  • Much faster and smaller
  • Not human-readable
  • Requires same serializer on both ends
  • Extension methods use Base64 encoding for string conversions

Shared Message Bus Topics

When using a shared message bus topic across multiple applications:

csharp

// All apps must use the SAME serializer
var serializer = new SystemTextJsonSerializer(); // or MessagePackSerializer

services.AddSingleton<ISerializer>(serializer);
services.AddSingleton<IMessageBus>(sp =>
    new RedisMessageBus(o =>
    {
        o.Topic = "events"; // Shared topic
        o.Serializer = sp.GetRequiredService<ISerializer>();
    }));

WARNING

If different applications use different serializers on the same topic, deserialization will fail. Coordinate serializer choice across all consumers.

Next Steps

  • Caching - Cache-specific serialization patterns
  • Queues - Queue serialization and message size considerations
  • Messaging - Message bus serialization and shared topics
  • Resilience - Configure resilience policies