---
url: /guide/serialization.md
---
# Serialization

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](https://github.com/FoundatioFx/Foundatio/blob/main/src/Foundatio/Serializer/ISerializer.cs)

```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](https://github.com/FoundatioFx/Foundatio/blob/main/src/Foundatio/Serializer/SystemTextJsonSerializer.cs)

```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

| Serializer | Speed | Size | Human Readable | Type Info | Dependencies |
|------------|-------|------|----------------|-----------|--------------|
| System.Text.Json | Fast | Medium | ✅ | ❌ | Built-in |
| MessagePack | **Very Fast** | **Small** | ❌ | Optional | MessagePack NuGet |
| Newtonsoft.Json | Medium | Medium | ✅ | ✅ | Newtonsoft.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](/guide/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:

| Component | Exception Type | Namespace |
|-----------|---------------|-----------|
| Caching | `CacheException` | `Foundatio.Caching` |
| Queues | `QueueException` | `Foundatio.Queues` |
| Messaging | `MessageBusException` | `Foundatio.Messaging` |
| Storage | `StorageException` | `Foundatio.Storage` |
| Serialization | `SerializerException` | `Foundatio.Serializer` |
| Resilience | `BrokenCircuitException` | `Foundatio.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](/guide/caching) - Cache-specific serialization patterns
* [Queues](/guide/queues) - Queue serialization and message size considerations
* [Messaging](/guide/messaging) - Message bus serialization and shared topics
* [Resilience](/guide/resilience) - Configure resilience policies
