Meziantou.Framework.Language.Toml 2.0.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Meziantou.Framework.Language.Toml --version 2.0.0
                    
NuGet\Install-Package Meziantou.Framework.Language.Toml -Version 2.0.0
                    
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="Meziantou.Framework.Language.Toml" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.Language.Toml" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Meziantou.Framework.Language.Toml" />
                    
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 Meziantou.Framework.Language.Toml --version 2.0.0
                    
#r "nuget: Meziantou.Framework.Language.Toml, 2.0.0"
                    
#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 Meziantou.Framework.Language.Toml@2.0.0
                    
#: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=Meziantou.Framework.Language.Toml&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Meziantou.Framework.Language.Toml&version=2.0.0
                    
Install as a Cake Tool

Meziantou.Framework.Language.Toml

An immutable TOML syntax tree that keeps every character of the text it was parsed from (comments, whitespace, quoting style) and lets you edit it without reformatting anything you did not touch. It reads TOML 1.1, the current version of the specification, and TOML 1.0 on request, and passes the whole toml-test conformance suite for both.

It is modelled on Roslyn. If you have used Microsoft.CodeAnalysis, everything here will look familiar: a SyntaxTree over a SourceText, nodes and tokens with spans, trivia, SyntaxKind, visitors, rewriters, and annotations.

Parsing never throws and never gives up. Whatever the text says, the tree reproduces it exactly, and anything wrong with it is reported through GetDiagnostics(): a missing bracket as much as a key defined twice.

using Meziantou.Framework.Language;
using Meziantou.Framework.Language.Toml;

var tree = TomlSyntaxTree.ParseText("""
    # The server
    [server]
    host = "localhost"
    ports = [ 8000, 8001 ]
    """);

Console.WriteLine(tree.GetDiagnostics().Count); // 0

var server = tree.GetRoot().Tables.Single();
var ports = (TomlArraySyntax)server.Properties[1].Value;
Console.WriteLine(((TomlIntegerSyntax)ports.Elements[0]).Value); // 8000

The shape of a tree

TomlDocumentSyntax
├── Entries : SyntaxList<TomlEntrySyntax>     (flat, in source order)
│   ├── TomlTableSyntax        →  OpenBracketToken, Key, CloseBracketToken     [server], or [[products]]
│   ├── TomlPropertySyntax     →  Key, EqualsToken, Value                      host = "localhost"
│   └── TomlSkippedTextSyntax  →  Tokens                                       a line that could not be read
└── EndOfFileToken

The entries are flat, as they are in the text: a table header is followed by the key/value pairs under it rather than being their parent. TomlTableSyntax.Properties gathers the pairs under a header, TomlDocumentSyntax.RootProperties the ones before the first header, and TomlTableSyntax.IsArrayOfTables tells [a] from [[a]].

A key is a TomlKeySyntax: the parts of a dotted key and the dots between them. Names has the name of each part with its quotes removed and its escape sequences resolved, so site."google.com" has the names site and google.com.

A value is one of:

Node Example Value
TomlStringSyntax "basic", 'literal', """multi-line""", '''multi-line''' string
TomlIntegerSyntax 42, -17, 1_000, 0xDEADBEEF, 0o755, 0b1101 long
TomlFloatSyntax 3.14, 6.626e-34, inf, nan double
TomlBooleanSyntax true, false bool
TomlDateTimeSyntax 1979-05-27T07:32:00Z DateTimeOffset
1979-05-27T07:32:00 DateTime
1979-05-27 DateOnly
07:32:00 TimeOnly
TomlArraySyntax [1, 2, 3] Elements, a separated list of values
TomlInlineTableSyntax { x = 1, y = 2 } Properties, a separated list of key/value pairs
TomlSkippedValueSyntax a value that could not be read, or that is missing Tokens

The commas of an array or an inline table belong to the list, not to what they follow: Count counts elements and SeparatorCount counts commas, so a trailing comma is simply a list where the two are equal.

Whitespace and comments are trivia. Up to and including the end of a line they belong to the token that ends that line, and everything after belongs to the token that follows, so a comment on its own line attaches to the entry it describes.

Editing without reformatting

Nodes are immutable: every change returns a new node, and everything you did not change keeps its text.

var root = TomlSyntaxTree.ParseText("port = 8080 # the port\n").GetRoot();
var port = (TomlIntegerSyntax)root.RootProperties[0].Value;

var updated = root.ReplaceNode(port, port.WithValue(9090));
Console.WriteLine(updated.ToFullString()); // port = 9090 # the port

SyntaxFactory builds new nodes. The methods that take .NET values write them the way TOML requires: keys are quoted when they cannot be bare, strings are escaped, and floats always read back as floats.

var document = SyntaxFactory.TomlDocument(
    SyntaxFactory.TomlTable("server"),
    SyntaxFactory.TomlProperty("host name", SyntaxFactory.TomlString("localhost")),
    SyntaxFactory.TomlProperty(SyntaxFactory.Key("limits", "max"), SyntaxFactory.TomlFloat(1)));

Console.WriteLine(document.ToFullString());
// [server]
// "host name" = "localhost"
// limits.max = 1.0

Every entry of a document has to end its line, so TomlDocument and AddEntries add a line feed to an entry that does not already end with one. SyntaxFactory.ParseValue reads a single value, which is the easiest way to get one written exactly as you want, such as a literal or multi-line string.

Diagnostics

Nothing is thrown for bad input; it is reported instead, with a location you can turn into line and character positions.

foreach (var diagnostic in TomlSyntaxTree.ParseText("a = 1\na = 2\n").GetDiagnostics())
{
    var line = diagnostic.Location.GetLineSpan().Start;
    Console.WriteLine($"{diagnostic.Id} ({line.Line},{line.Character}): {diagnostic.Message}");
    // TOML0020 (1,0): The key 'a' is already defined.
}
Id Reported for
TOML0001 A missing key
TOML0002 A missing ], ]], }, =, or ,, or a ] where ]] was expected
TOML0003 A missing value
TOML0004 Something after an entry on its line, where only a comment may follow
TOML0005 A token that cannot start an entry or a value
TOML0006 A malformed value: a number, date, or time that breaks the grammar, an integer that does not fit in 64 bits, a word that is not a value
TOML0007 A bare key with a character it cannot hold, or a multi-line string used as a key
TOML0008 An unterminated string
TOML0009 An invalid escape sequence, or one that is not a Unicode scalar value
TOML0010 A control character, or whitespace TOML does not allow, such as a non-breaking space
TOML0011 A carriage return not followed by a line feed
TOML0012 Arrays and inline tables nested deeper than TomlParseOptions.MaxDepth (128 by default)
TOML0013 A TOML 1.1 feature when parsing as TOML 1.0
TOML0020 A key defined twice
TOML0021 A table defined twice
TOML0022 An addition to an inline table or an array, which cannot be extended after the fact
TOML0023 A table header or a dotted key that goes through a value
TOML0024 A dotted key that adds to a table a header already defined

Versions

TomlParseOptions.Version chooses the version of the specification. TOML 1.1, the default, adds line breaks and a trailing comma in inline tables, the \e and \xHH escape sequences, and times written without seconds. Parsing as TOML 1.0 reports each of them as TOML0013 and otherwise reads them the same way.

var tree = TomlSyntaxTree.ParseText(text, new TomlParseOptions { Version = TomlVersion.V1_0 });

Recovering from mistakes

TOML is line-oriented, and so is recovery: a key/value pair or a table header that goes wrong never takes the next line with it. A string without its closing quote ends with its line. Arrays, and inline tables in TOML 1.1, may span lines, so they cannot stop at the end of one; they stop at their closing bracket, and also at a line that can only be the start of the next entry, a table header or key =, where a comma or a closing bracket was expected. A missing ] is reported once, rather than turning the rest of the document into one array.

What .NET cannot hold

Values are read into .NET types, which cannot hold a few valid TOML values. The year 0 and a leap second (:60) are reported as TOML0006. An offset further from UTC than ±14:00 is read as the same instant in UTC. A fraction of a second is truncated to the seven digits .NET keeps, as the specification asks. Line breaks in multi-line strings are read as line feeds, whatever the text uses.

Walking a tree

TomlSyntaxWalker visits every node, and with SyntaxWalkerDepth.Token or SyntaxWalkerDepth.Trivia every token and trivium too. TomlSyntaxRewriter builds a new tree from the nodes you return; a node whose parts all come back unchanged is returned as it was.

private sealed class DoubleIntegers : TomlSyntaxRewriter
{
    public override SyntaxNode? VisitTomlInteger(TomlIntegerSyntax node) => node.WithValue(node.Value * 2);
}

Coming from version 1

Version 1 kept every value and key as raw text. Version 2 parses them:

  • TomlPropertySyntax.Key is a TomlKeySyntax and Value a TomlValueSyntax, where they were strings. KeyToken, SeparatorToken, and ValueNode are replaced by Key, EqualsToken, and Value.
  • TomlTableSyntax.Key replaces NameToken and Name. An array-of-tables header has the kind SyntaxKind.TomlArrayOfTables, and [[ and ]] are the tokens OpenBracketOpenBracketToken and CloseBracketCloseBracketToken.
  • SyntaxKind.KeyToken and SyntaxKind.ValueToken are replaced by a kind per key and value token, and TomlArraySyntax.Contents by Elements.
  • SyntaxFactory.TomlProperty(string, string) is TomlProperty(string, TomlValueSyntax), and Key(string) and Value(string) are replaced by Key(params string[]), KeyPart, Literal, and ParseValue.
Product Compatible and additional computed target framework versions.
.NET 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.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Meziantou.Framework.Language.Toml:

Package Downloads
Meziantou.Framework.DependencyScanning

Find dependencies in source files. Support multiple package managers such as NuGet, npm, Docker, PyPi, and so on

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.3 40 10/18/2026
2.0.2 235 9/30/2026
2.0.1 107 9/27/2026
2.0.0 95 9/25/2026
1.0.0 97 9/24/2026