Tomlyn supports an incremental source generator for high-performance, NativeAOT-friendly serialization. It follows the same patterns as System.Text.Json source generation.

When to use source generation

  • Publishing with PublishAot=true (NativeAOT).
  • Trimming aggressively (PublishTrimmed=true).
  • Avoiding reflection overhead on hot paths.
  • Wanting compile-time validation of serialization metadata.

Define a context

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 generated metadata

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);

Compile-time options

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 { }

Registering converters at compile time

[TomlSourceGenerationOptions(
    Converters = [typeof(MyCustomConverter)])]
[JsonSerializable(typeof(ServerConfig))]
internal partial class MyTomlContext : TomlSerializerContext { }

Naming policy and generated code

For source generation, member names are resolved at build time using:

  1. TomlPropertyNameAttribute / JsonPropertyNameAttribute (when present - these override naming policies).
  2. TomlSourceGenerationOptionsAttribute.PropertyNamingPolicy (when no explicit name attribute).

The generated serializer stores the resolved names directly and does not call ConvertName(...) at runtime.

Supported attributes (source generation)

The source generator supports these attributes at compile time:

Member-level Type-level
JsonPropertyNameAttribute / TomlPropertyNameAttribute JsonConstructorAttribute / TomlConstructorAttribute
JsonIgnoreAttribute / TomlIgnoreAttribute JsonPolymorphicAttribute / TomlPolymorphicAttribute
JsonIncludeAttribute / TomlIncludeAttribute JsonDerivedTypeAttribute / TomlDerivedTypeAttribute
JsonPropertyOrderAttribute / TomlPropertyOrderAttribute
JsonRequiredAttribute / TomlRequiredAttribute
JsonExtensionDataAttribute / TomlExtensionDataAttribute

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.

Reflection control

Reflection fallback can be disabled globally before first serializer use:

AppContext.SetSwitch("Tomlyn.TomlSerializer.IsReflectionEnabledByDefault", false);

When reflection is disabled:

You can also disable reflection via MSBuild in your project file:

<PropertyGroup>
  <TomlSerializerIsReflectionEnabledByDefault>false</TomlSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

Multiple contexts

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 { }

Troubleshooting

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.