MinimalSerializers.Json 1.0.5

dotnet add package MinimalSerializers.Json --version 1.0.5
                    
NuGet\Install-Package MinimalSerializers.Json -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MinimalSerializers.Json" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MinimalSerializers.Json" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="MinimalSerializers.Json" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MinimalSerializers.Json --version 1.0.5
                    
#r "nuget: MinimalSerializers.Json, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MinimalSerializers.Json@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MinimalSerializers.Json&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=MinimalSerializers.Json&version=1.0.5
                    
Install as a Cake Tool

MinimalSerializers.Json

Automatically generate System.Text.Json source-generation roots from [DataContract] graphs.

Stop hand-maintaining hundreds of [JsonSerializable(typeof(...))] attributes (and the T[] / List<T> variants ASP.NET needs). Mark your DTOs with [DataContract] / [DataMember], put [MinimalJsonSerializerContext] on a partial JsonSerializerContext, and let the package emit the roots before CoreCompile so the built-in STJ source generator produces serializers in a single dotnet build.

Why this exists

JsonSerializerContext is the right AOT-friendly path, but root registration is manual and brittle:

  • every DTO
  • every nested type
  • every List<T> / T[] / dictionary shape used as a root

MinimalSerializers.Json fills the gap .NET should have shipped: discover the graph, emit the roots, reuse STJ.

How it works

[DataContract] models + partial context [MinimalJsonSerializerContext]
        │
        ▼
MSBuild target (buildTransitive) → discovery task
        │
        ▼
obj/.../*.MinimalJson.g.cs   // [JsonSerializable(typeof(T))] + arrays/lists
        │
        ▼
CoreCompile → System.Text.Json source generator
        │
        ▼
Context.Default  (same runtime path as hand-written contexts)

There is no generator ordering hack. Roslyn generators cannot see each other's main outputs, so this package writes compile-visible source via MSBuild before STJ runs.

Runtime performance is the same as a complete manual STJ context, because the serializers are still generated by STJ.

Install

dotnet add package MinimalSerializers.Json

No custom Target in your csproj. The package ships buildTransitive props/targets automatically.

Quick start

using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MinimalSerializers.Json;

[DataContract]
public sealed record OrderDto
{
    [DataMember] public required Guid Id { get; init; }
    [DataMember] public required List<OrderItemDto> Items { get; init; }
}

[DataContract]
public sealed record OrderItemDto
{
    [DataMember] public required string Sku { get; init; }
    [DataMember] public required int Quantity { get; init; }
}

[MinimalJsonSerializerContext]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public partial class AppJsonSerializerContext : JsonSerializerContext;

Wire into options / DI

using System.Text.Json;
using MinimalSerializers.Json;

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
options.AddMinimalJsonContext<AppJsonSerializerContext>();

// ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(o =>
{
    o.SerializerOptions.AddMinimalJsonContext<AppJsonSerializerContext>();
});

Or insert the context yourself (same as today):

options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);

What gets registered

For each discovered object/enum type T (from [DataContract] and nested [DataMember] graphs):

  • T
  • T[] (default on — important because ASP.NET often materializes arrays)
  • List<T> (default on)

Also registers closed collection/dictionary shapes found on members when enabled.

TypeInfo property names

Collection and array roots are emitted with an explicit TypeInfoPropertyName so they cannot collide with DTOs named List*Dto (STJ SYSLIB1031):

[JsonSerializable(typeof(ListMoneyDetailsDto))]
[JsonSerializable(typeof(ListMoneyDetailsDto[]), TypeInfoPropertyName = "ArrayOf_ListMoneyDetailsDto")]
[JsonSerializable(typeof(List<MoneyDetailsDto>), TypeInfoPropertyName = "ListOf_MoneyDetailsDto")]

Plain object/enum roots keep STJ's default short names unless two types would still share the same short name (different namespaces), in which case Minimal assigns unique names. Prefer options.GetTypeInfo(typeof(T)) / JsonSerializer.Serialize(value, options) when you do not want to depend on generated property names; typed Context.Default.ListOf_… members follow the mangled names above.

Conventions

Mark Role
[DataContract] Include type in discovery
[DataMember] Include member in graph walk (if any DataMembers exist on the type)
[IgnoreDataMember] / [JsonIgnore] Skip member
[EnumMember] Preserved for STJ enum handling where applicable
[MinimalJsonSerializerContext] Partial context to receive generated roots

One context per project is the intended model (for example one DTO/project assembly → one context).

MSBuild properties

Property Default Meaning
MinimalJsonSerializerEnabled true Master switch
MinimalJsonEmitArrays true Emit T[] roots
MinimalJsonEmitList true Emit List<T> roots
MinimalJsonEmitDeclaredCollections true Emit declared collection interface closed types
MinimalJsonEmitDictionaries true Emit dictionary closed types
MinimalJsonWarnOpenGenerics summary MSJ0004 mode: summary (one warning), all (per type), or none
MinimalJson_EnableDesignTime true Generate during design-time builds

Performance

The package does not invent a new serializer. It discovers your DataContract graph and emits the same [JsonSerializable] roots you would have written by hand, then the built-in System.Text.Json source generator builds the serializers.

So the fair comparison is three paths on the same payload:

Path What it is
Reflection Stock STJ with DefaultJsonTypeInfoResolver — no JsonSerializerContext, no hand-written roots
Manual STJ Complete hand-maintained [JsonSerializable(typeof(...))] context (the maintenance pain)
MinimalSerializers Same STJ source-gen path as manual, but roots are discovered automatically

Benchmarks use a warmed order with 64 line items and include both:

  • options/resolver-chain APIs (Serialize(value, options) / Deserialize<T>(json, options))
  • typed source-gen APIs (Serialize(value, Context.Default.T) / Deserialize(json, Context.Default.T))

Results

Representative numbers from benchmarks/MinimalSerializers.Json.Benchmarks on .NET 11 / Apple M4 Max (InProcess, warmed):

Method Mean Allocated vs reflection serialize
Reflection_Serialize (baseline) 21.1 µs 5.65 KB 1.00×
ManualStj_Serialize 9.4 µs 5.34 KB 0.55×
MinimalStj_Serialize 11.0 µs 5.34 KB 0.64×
ManualStj_Serialize_TypeInfo 3.5 µs 5.34 KB 0.20×
MinimalStj_Serialize_TypeInfo 4.4 µs 5.34 KB 0.25×
Reflection_Deserialize 11.2 µs 11.2 KB 0.65×
ManualStj_Deserialize 15.1 µs 18.4 KB 0.88×
MinimalStj_Deserialize 14.1 µs 18.4 KB 0.81×
ManualStj_Deserialize_TypeInfo 15.7 µs 18.4 KB 0.91×
MinimalStj_Deserialize_TypeInfo 14.5 µs 18.4 KB 0.84×

Takeaways

  1. Minimal ≈ Manual on both serialize and deserialize (within noise). That is the point: you get the source-generated STJ path without typing every root and every T[] / List<T> by hand.
  2. Serialize is a clear win for source-gen. Prefer the typed API (Context.Default.MyDto) when you can; it is roughly 4–6× faster than reflection here. Options-based source-gen is still about 2× faster than reflection.
  3. Deserialize is not automatically faster than reflection on warm, property-based DTOs. Reflection’s cached metadata path can allocate less and finish sooner on this shape. Manual and Minimal stay locked together, so the gap is STJ source-gen vs reflection — not Minimal overhead.
  4. Prefer source-gen anyway for AOT/trimming, ASP.NET root coverage (T / T[] / List<T>), and predictable startup. Minimal’s product win is authoring: one [MinimalJsonSerializerContext] partial instead of a brittle attribute list, at the same runtime speed as the hand-written context.
dotnet run --project benchmarks/MinimalSerializers.Json.Benchmarks -c Release

Samples

  • samples/Sample.Contracts — sample DataContract models + minimal context
  • samples/Sample.Host — sample host that serializes/deserializes with options wiring
dotnet run --project samples/Sample.Host

Limitations (v1)

  • Open generics are skipped as roots (closed constructed types only)
  • Generic DTO inheritance of the form Derived<T> : Base<T> (both DataContracts) can break the built-in STJ source generator with CS0102 (duplicate nested accessor types). MinimalSerializers emits MSJ0009 when it detects this shape. Prefer composition or flattened records. Closed constructions are still registered.
  • Polymorphism / $type discriminators are not auto-generated (keep your own JsonTypeInfo modifiers)
  • DataMember.Name is not rewritten to [JsonPropertyName] (STJ naming policies remain source of truth)
  • Multi-assembly discovery is not enabled by default (one project → one context)

Diagnostics

Code Meaning
MSJ0001 No Minimal context / attribute missing
MSJ0002 Context found but no DataContracts
MSJ0003 Context not partial or not a JsonSerializerContext
MSJ0004 Open generic skipped (summary / all / none via MinimalJsonWarnOpenGenerics)
MSJ0005 Unresolvable member type skipped
MSJ0006 Generation failure
MSJ0009 Generic DataContract inheritance Derived<T> : Base<T> may break STJ source-gen (CS0102)

Development

dotnet tool restore
dotnet build MinimalSerializers.slnx
dotnet test MinimalSerializers.slnx --settings coverage.runsettings
dotnet pack src/MinimalSerializers.Json/MinimalSerializers.Json.csproj -c Release -o artifacts/packages

Maintainer publish steps (Trusted Publishing / local push) live in PUBLISHING.md.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net11.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 132 8/19/2026
1.0.4 110 8/14/2026
1.0.3 109 8/7/2026
1.0.2 105 8/7/2026
1.0.1 114 7/31/2026
1.0.0 123 7/31/2026

# Release Notes

## 1.0.5

- Invalidate `stamp.minimaljson` before the incremental skip when `*.MinimalJson.g.cs` is missing, so a leftover stamp cannot produce CS0534 (#7)

## 1.0.4

- MSJ0004 no longer warns for abstract open `[DataContract]` generics (they are still skipped as roots). Closed instantiations stay registered.

## 1.0.3

- Speed up package acceptance tests: pack once per class, disable parallelization (avoids CI hang from concurrent Tasks rebuilds)
- Includes 1.0.2 fixes: TypeInfoPropertyName for collection roots, MSJ0009 generic inheritance diagnostic, quieter MSJ0004

## 1.0.2

- Assign `TypeInfoPropertyName` for `List<T>` / array / collection roots to avoid SYSLIB1031 collisions with `List*Dto` types (#2)
- Detect generic DataContract inheritance `Derived<T> : Base<T>` and emit MSJ0009 guidance for the STJ CS0102 footgun (#3)
- Default MSJ0004 open-generic warnings to a single summary; configure with `MinimalJsonWarnOpenGenerics=summary|all|none` (#5)
- Package + unit regression tests for `List*Dto` collisions and generic DTO inheritance (#4)

## 1.0.1

- Ship package README and release notes on nuget.org
- README cleanup: generic samples/docs; publishing moved to PUBLISHING.md

## 1.0.0

- Initial release of MinimalSerializers.Json
- MSBuild pre-compile discovery of `[DataContract]` graphs
- Emits `[JsonSerializable]` roots including `T`, `T[]`, and `List<T>`
- `[MinimalJsonSerializerContext]` attribute and `AddMinimalJsonContext<T>()` helper
- Single-build guarantee with buildTransitive packaging
- Samples, tests, benchmarks, CI/release workflows