Tomlyn supports an incremental source generator for high-performance, NativeAOT-friendly serialization.
It follows the same patterns as System.Text.Json source generation.
Source generation is recommended for production applications. It eliminates reflection, enables trimming, and catches serialization configuration errors at compile time.
PublishAot=true (NativeAOT).PublishTrimmed=true).Declare a partial class that inherits from TomlSerializerContext and annotate it with [JsonSerializable] for each root type:
using System.Text.Json.Serialization;
using Tomlyn.Serialization;
public sealed class ServerConfig
{
public string Host { get; set; } = "localhost";
public int Port { get; set; } = 8080;
}
[JsonSerializable(typeof(ServerConfig))]
internal partial class MyTomlContext : TomlSerializerContext { }
The generator produces a Default singleton and a typed TomlTypeInfo<T> property for each root.
Nested types referenced by the root are discovered transitively - you only need to annotate top-level types.
Use the generated TomlTypeInfo<T> property directly (recommended):
using Tomlyn;
var context = MyTomlContext.Default;
var toml = TomlSerializer.Serialize(config, context.ServerConfig);
var roundTrip = TomlSerializer.Deserialize(toml, context.ServerConfig);
For APIs that take a Type, pass the context:
var toml = TomlSerializer.Serialize(value, typeof(ServerConfig), context);
var roundTrip = TomlSerializer.Deserialize(toml, typeof(ServerConfig), context);
Use TomlSourceGenerationOptionsAttribute to fix serialization options at build time:
using System.Text.Json;
using System.Text.Json.Serialization;
using Tomlyn;
using Tomlyn.Serialization;
[TomlSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
WriteIndented = true,
IndentSize = 2,
DefaultIgnoreCondition = TomlIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(ServerConfig))]
internal partial class MyTomlContext : TomlSerializerContext { }
[TomlSourceGenerationOptions(
Converters = [typeof(MyCustomConverter)])]
[JsonSerializable(typeof(ServerConfig))]
internal partial class MyTomlContext : TomlSerializerContext { }
For source generation, member names are resolved at build time using:
TomlPropertyNameAttribute / JsonPropertyNameAttribute (when present - these override naming policies).TomlSourceGenerationOptionsAttribute.PropertyNamingPolicy (when no explicit name attribute).The generated serializer stores the resolved names directly and does not call ConvertName(...) at runtime.
The source generator supports these attributes at compile time:
JsonConverterAttribute / TomlConverterAttribute is supported by the reflection resolver but is not modeled by generated metadata.
For source-generated scenarios, register converters via TomlSourceGenerationOptionsAttribute.Converters or TomlSerializerOptions.Converters.
Using [TomlConverter] or [JsonConverter] attributes with source generation will silently fall back to reflection.
Always register converters via TomlSourceGenerationOptionsAttribute.Converters for AOT safety.
Reflection fallback can be disabled globally before first serializer use:
AppContext.SetSwitch("Tomlyn.TomlSerializer.IsReflectionEnabledByDefault", false);
When reflection is disabled:
TomlTypeInfo<T> or TomlSerializerOptions.TypeInfoResolver.TomlTable, TomlArray) remain supported without reflection.You can also disable reflection via MSBuild in your project file:
<PropertyGroup>
<TomlSerializerIsReflectionEnabledByDefault>false</TomlSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
You can define multiple contexts for different parts of your application:
[JsonSerializable(typeof(ServerConfig))]
internal partial class ServerContext : TomlSerializerContext { }
[JsonSerializable(typeof(DatabaseConfig))]
internal partial class DatabaseContext : TomlSerializerContext { }
| Problem | Solution |
|---|---|
| Generated properties are missing | Ensure the project references the Tomlyn NuGet package (the generator is shipped under analyzers/dotnet/cs). |
| Compilation errors on context | Ensure the context class is partial. |
| Type not found on context | Ensure root types are declared via [JsonSerializable(typeof(...))]. |
| Nested types not serialized | Nested types are discovered transitively - verify they're reachable from a root type. |
| Converter not applied | Use TomlSourceGenerationOptionsAttribute.Converters instead of TomlConverterAttribute for source-generated contexts. |