Getting started
Install
Add the package. Its one library dependency, the shared Bodu.Text.Serialization package (the attribute family, naming policies, and callback interfaces), is restored transitively - there is nothing else to add.
dotnet add package Bodu.Text.Toml
It targets net8.0.
A first TOML round trip
using Bodu.Text.Toml;
public sealed class ServerConfig
{
public string Host { get; set; } = "";
public int Port { get; set; }
public bool Secure { get; set; }
}
var config = new ServerConfig { Host = "localhost", Port = 8080, Secure = true };
string text = TomlSerializer.Serialize(config);
// Host = "localhost"
// Port = 8080
// Secure = true
ServerConfig back = TomlSerializer.Deserialize<ServerConfig>(text);
The native value model in action
TOML carries real scalar kinds, so the common BCL types land on the right wire form with no string conventions to invent. The four RFC 3339 date-time types and the float specials are the clearest illustration:
using Bodu.Text.Toml;
public sealed class Telemetry
{
public DateTimeOffset CapturedAt { get; set; } // offset date-time
public DateOnly Day { get; set; } // local date
public TimeOnly At { get; set; } // local time
public double Ratio { get; set; } // float (incl. inf / nan)
public long Samples { get; set; } // integer (signed 64-bit)
}
var telemetry = new Telemetry
{
CapturedAt = new DateTimeOffset(2026, 6, 28, 9, 30, 0, TimeSpan.FromHours(10)),
Day = new DateOnly(2026, 6, 28),
At = new TimeOnly(9, 30, 0),
Ratio = double.PositiveInfinity,
Samples = 4096,
};
string text = TomlSerializer.Serialize(telemetry);
// CapturedAt = 2026-06-28T09:30:00+10:00
// Day = 2026-06-28
// At = 09:30:00
// Ratio = inf
// Samples = 4096
DateTimeOffset, DateTime (Unspecified), DateOnly, and TimeOnly map one-to-one onto offset date-time, local date-time, local date, and local time. The full per-type catalogue, including the decimal and byte[] representation choices, is in the type-mapping table and the built-in converter catalog.
Rename members
using Bodu.Text.Serialization;
using Bodu.Text.Toml;
var options = new TomlSerializerOptions
{
PropertyNamingPolicy = NamingPolicy.SnakeCaseLower,
};
// "Host" is written as "host", "Port" as "port".
string text = TomlSerializer.Serialize(config, options);
Or pin a single member's name with [PropertyName].
Edit a document without a model
When you need to change a value but do not want a POCO, parse to the mutable DOM:
using Bodu.Text.Toml.Nodes;
TomlNode node = TomlNode.Parse(utf8Toml)!;
node["server"]!["port"] = 9090;
byte[] back = node.ToUtf8Bytes();
Read a document without a model
For inspection only, the read-only DOM is the lighter choice - a low-allocation view over the parsed buffer, walked through RootElement:
using Bodu.Text.Toml.Document;
string toml = """
[server]
host = "localhost"
port = 8080
""";
using TomlDocument doc = TomlDocument.Parse(toml);
TomlElement server = doc.RootElement.GetProperty("server");
string host = server.GetProperty("host").GetString(); // "localhost"
long port = server.GetProperty("port").GetInt64(); // 8080
TomlDocument is disposable - wrap it in using and copy out any values that must outlive it.
Round-trip through a Stream
TomlSerializer reads and writes Stream directly, with async variants:
using Bodu.Text.Toml;
await using (FileStream stream = File.Create("server.toml"))
{
await TomlSerializer.SerializeAsync(stream, config);
}
await using (FileStream stream = File.OpenRead("server.toml"))
{
ServerConfig loaded = await TomlSerializer.DeserializeAsync<ServerConfig>(stream);
}
Synchronous Stream overloads are also provided.
When something goes wrong
Failures split into two exception types, so you can tell bad input apart from wrong type:
- A malformed document - input the grammar rejects - raises TomlFormatException. Because TOML files are edited by hand, the exception carries the line, column, and offset of the failure.
- A document that parses but cannot bind to your type - a type mismatch, a missing required member, a value the format cannot represent - raises TomlSerializationException.
try
{
ServerConfig loaded = TomlSerializer.Deserialize<ServerConfig>(text);
}
catch (TomlFormatException ex)
{
Console.Error.WriteLine($"Malformed TOML at line {ex.LineNumber}, column {ex.ColumnNumber}: {ex.Message}");
}
catch (TomlSerializationException ex)
{
Console.Error.WriteLine($"Document does not match ServerConfig: {ex.Message}");
}
Where to go next
- Bodu.Text.Toml introduction - what is specific to TOML: the rich value model, spec versions, positioned diagnostics.
- Core concepts - the TOML vocabulary, including the full value-mapping table.
- Using TOML - type mapping, spec versions, the DOMs, and streams.
- Writing converters - custom shapes with
TomlConverter<T>. - Text & Serialization topic overview - where the serializers sit among the codecs and document formats.