Serializable Property Analyzer¶
Verified by tests
SerializablePropertyAnalyzerTests — library CI run #31657041675 (2026-08-13)
The SerializablePropertyAnalyzer is a Roslyn analyzer that detects non-serializable properties on ICommand and IEvent types. It ensures AOT compatibility by flagging object, dynamic, and non-generic interface properties that cannot be serialized without runtime reflection.
Overview¶
This analyzer runs during compilation and checks:
- Direct properties on message types (
ICommand,IEvent, or[WhizbangSerializable]) - Nested types - recursively checks child type properties
- Collection elements - validates element types in arrays and generic collections
Only public types are analyzed — non-public types can't be serialized by the generated JSON contexts anyway. Properties inherited from base classes are included in the check.
Supported Diagnostics¶
| ID | Severity | Description |
|---|---|---|
| WHIZ060 | Error | Property uses non-serializable type object |
| WHIZ061 | Error | Property uses non-serializable type dynamic |
| WHIZ062 | Error | Property uses non-serializable interface type |
| WHIZ063 | Error | Nested type contains non-serializable property |
Why This Matters¶
System.Text.Json with source generation (AOT) cannot serialize:
objectproperties - Runtime type is unknown at compile timedynamicproperties - Requires DLR and runtime reflection- Non-generic interfaces - Cannot determine concrete type at compile time
Using these types in messages causes runtime serialization failures in AOT-compiled applications.
Example: Valid vs Invalid Properties¶
Valid (AOT-Compatible)¶
Valid (AOT-Compatible)
public record CreateOrderCommand : ICommand {
public Guid OrderId { get; init; }
public string CustomerName { get; init; } = string.Empty;
public decimal TotalAmount { get; init; }
public List<OrderLineItem> Items { get; init; } = [];
public IReadOnlyList<string> Tags { get; init; } = []; // Generic interface is OK
}
Note that the analyzer flags non-generic interfaces only; a generic interface like IDictionary<string, object> is not flagged even though its object values would still fail AOT serialization at runtime — prefer concrete value types there too.
Invalid (Causes Diagnostics)¶
Invalid (Causes Diagnostics)
public record BadCommand : ICommand {
public object Payload { get; init; } // WHIZ060
public dynamic Data { get; init; } // WHIZ061
public IEnumerable Items { get; init; } // WHIZ062 (non-generic)
}
Nested Type Checking¶
The analyzer recursively validates nested types:
Nested Type Checking
public record OrderCreated : IEvent {
[StreamId]
public Guid OrderId { get; init; }
public CustomerInfo Customer { get; init; } // Nested type checked
}
public record CustomerInfo {
public string Name { get; init; } = string.Empty;
public object Metadata { get; init; } // WHIZ063: Nested violation
}
Whizbang Serializable Attribute¶
Types marked with [WhizbangSerializable] are also analyzed:
Whizbang Serializable Attribute
[WhizbangSerializable]
public record ProductDto {
public Guid Id { get; init; }
public object Extra { get; init; } // WHIZ060: Still flagged
}
Configuration¶
The analyzer is enabled by default. To suppress specific diagnostics:
Per-Property Suppression¶
Per-Property Suppression
#pragma warning disable WHIZ060
public object LegacyData { get; init; }
#pragma warning restore WHIZ060
Project-Wide Suppression¶
In an .editorconfig (NoWarn does not suppress Error-severity diagnostics):
Project-Wide Suppression
[*.cs]
dotnet_diagnostic.WHIZ060.severity = none
dotnet_diagnostic.WHIZ061.severity = none
dotnet_diagnostic.WHIZ062.severity = none
dotnet_diagnostic.WHIZ063.severity = none
Related Diagnostics¶
- WHIZ060 - Property uses
objecttype - WHIZ061 - Property uses
dynamictype - WHIZ062 - Property uses non-generic interface
- WHIZ063 - Nested type contains non-serializable property
See Also¶
- AOT Compatibility - AOT design principles
- Messages - ICommand and IEvent documentation
- JSON Contexts - How Whizbang generates AOT-compatible serialization