On this page
#go-toml-edit
Zero-dep TOML editing library for Go with comment preservation
#API Reference
The API reference documents every exported type, function, and method in the tomledit package, including the parser, document editing operations such as Set, Delete, and RenameKey, the read-layer types Record and Entry, the cursor navigation API, comment reading and writing methods, document diffing and merging, strict decoding into Go values and against a descriptor, and the Format function for canonical output styling.
#.
Package tomledit: Zero-dep TOML editing library for Go with comment preservation.
The tree preserves comments, whitespace and formatting. Values can be read, set, deleted and renamed without disturbing unrelated parts of the file. A document is serialized back with [Document.Bytes] (round-trip fidelity) or reformatted with [Document.Format].
#Two surfaces, two questions
A document is readable through two surfaces, and which one to use follows from the question being asked.
The SYNTACTIC surface is the AST: [Document.Walk], [Document.Resolve] and the concrete node types. It answers what the file CONTAINS, in the form it was written -- spellings, quoting styles, integer bases, comments, blank lines and the source span of every construct. A value written as an inline table arrives as an [*InlineTableNode]; one written under a header arrives as that header's children.
The LOGICAL surface is the read-layer: [Document.Root] returns a [Record], whose [Entry] values are the document's keys in first-appearance order. It answers what the document MEANS -- values, structure and order with the spellings folded away. A dotted key, a [header] table and an inline table are indistinguishable through it, and so is a table only a longer header implies.
Read values through the read-layer or the typed accessors; use the AST to edit, or to inspect how something was written. Reads are spelling-blind; writes are structurally conservative -- a value write touches value fragments and nothing else, and a structural construct changes only through a structural operation or an explicit [Document.Delete].
#Strictness and diagnostics
Decoding is strict and strictness is the only mode: an unknown key, an unknown table, a value of a refused kind, a value the target cannot hold exactly, and a missing required key are all errors. There is no lenient mode and no option to skip a check.
Every failure that depends on the document -- from parsing, decoding, editing or access alike -- is an [Error] carrying a kind, a document path and a position, matchable with errors.Is against the [ErrSyntax] family of kind sentinels and readable with errors.As. A decode reports every independent violation together as an [Errors] aggregate in document order; parsing reports its first failure alone, because a parse cannot continue past a syntax error.
#Capabilities
- Lossless round-trip: parse and re-serialize without losing comments or formatting. - Path-based access: read and write values through paths such as "server.host", "items[0]" and "items[-1]". - Structural editing: create tables and array-of-tables, reorder children, append to and remove from arrays, rename and delete keys, seed defaults. - Strict decoding: into Go structs through [Unmarshal], [Decode], [DecodeNode] and [DecodeOver], or against a hand-built descriptor through [Document.Validate] and [Document.DecodeSpec]. - Diff and merge: compare two documents, or merge one into another. - Files: [ParseFile] remembers the filename for later diagnostics, and [Document.WriteFile] writes atomically after checking that the rendered bytes survive a round trip.
#Added
const Added ChangeKind = iota // Added means the key exists in b but not in a.#Removed
const Removed // Removed means the key exists in a but not in b.#Modified
const Modified // Modified means the key exists in both but with different values.#KindSyntax
const KindSyntax ErrorKind = iotaKindSyntax is a lexing or parsing failure.
#KindUnknownKey
const KindUnknownKeyKindUnknownKey is a decoded key matching no field of the target.
#KindUnknownTable
const KindUnknownTableKindUnknownTable is a decoded table or array-of-tables matching no field of the target; the diagnostic's Keys field lists the direct child keys of the offending construct.
#KindMissingKey
const KindMissingKeyKindMissingKey is a required key that the document does not carry.
#KindTypeMismatch
const KindTypeMismatchKindTypeMismatch is a value whose kind is not acceptable for the target; the diagnostic's Expected and Got fields name both sides.
#KindInexact
const KindInexactKindInexact is a value the target cannot hold exactly: an out-of-range integer, a lossy conversion, or a length mismatch. The diagnostic's Value field carries the offending value.
#KindNotFound
const KindNotFoundKindNotFound is a path naming nothing in the document.
#KindBadPath
const KindBadPathKindBadPath is a syntactically invalid path.
#KindWrongContainer
const KindWrongContainerKindWrongContainer is a path step that is structurally inapplicable: a key on an array, an index on a scalar, a concrete-node operation on a path that names no single node.
#KindBadInput
const KindBadInputKindBadInput is an invalid input value to an editing operation.
#KindConflict
const KindConflictKindConflict is an edit refused because it would produce an invalid document.
#KindRoundTrip
const KindRoundTripKindRoundTrip is a write whose rendered bytes did not survive a re-parse; the diagnostic's Offset field carries the byte offset of the first divergence.
#NodeDocument
const NodeDocument NodeType = iota // NodeDocument is the root document node.#NodeTable
const NodeTable // NodeTable is a [table] header node.#NodeArrayTable
const NodeArrayTable // NodeArrayTable is an [[array-table]] header node.#NodeKeyValue
const NodeKeyValue // NodeKeyValue is a key = value pair.#NodeKey
const NodeKey // NodeKey is a (possibly dotted) key.#NodeString
const NodeString // NodeString is a string value.#NodeInteger
const NodeInteger // NodeInteger is an integer value.#NodeFloat
const NodeFloat // NodeFloat is a float value.#NodeBoolean
const NodeBoolean // NodeBoolean is a boolean value.#NodeDateTime
const NodeDateTime // NodeDateTime is an offset date-time value.#NodeLocalDateTime
const NodeLocalDateTime // NodeLocalDateTime is a local date-time value (no timezone).#NodeLocalDate
const NodeLocalDate // NodeLocalDate is a local date value.#NodeLocalTime
const NodeLocalTime // NodeLocalTime is a local time value.#NodeArray
const NodeArray // NodeArray is an array value.#NodeInlineTable
const NodeInlineTable // NodeInlineTable is an inline table value.#NodeComment
const NodeComment // NodeComment is a standalone comment line.#StringBasic
const StringBasic StringStyle = iota // StringBasic is a double-quoted string ("...").#StringLiteral
const StringLiteral // StringLiteral is a single-quoted string ('...').#StringMultiLineBasic
const StringMultiLineBasic // StringMultiLineBasic is a triple-double-quoted string ("""...""").#StringMultiLineLiteral
const StringMultiLineLiteral // StringMultiLineLiteral is a triple-single-quoted string ('''...''').#IntegerDecimal
const IntegerDecimal IntegerBase = iota // IntegerDecimal is base-10 (e.g. 42).#IntegerHex
const IntegerHex // IntegerHex is base-16 (e.g. 0xFF).#IntegerOctal
const IntegerOctal // IntegerOctal is base-8 (e.g. 0o77).#IntegerBinary
const IntegerBinary // IntegerBinary is base-2 (e.g. 0b1010).#SegmentKey
const SegmentKey SegmentKind = iotaSegmentKey addresses a child by name.
#SegmentIndex
const SegmentIndexSegmentIndex addresses an element by position. A negative index counts from the end, so -1 is the last element.
#EntryValue
const EntryValue EntryKind = iotaEntryValue is a scalar or a plain array: one concrete value node.
#EntryRecord
const EntryRecordEntryRecord is a table in any spelling -- a header table, an inline table, or a table implied by a longer header or a dotted key.
#EntryRecords
const EntryRecordsEntryRecords is an array-of-tables: the entries collected under one key, in document order.
#FieldKindString
const FieldKindString FieldKind = iotaFieldKindString expects a string.
#FieldKindInteger
const FieldKindIntegerFieldKindInteger expects an integer.
#FieldKindFloat
const FieldKindFloatFieldKindFloat expects a float, or an integer that a float holds exactly.
#FieldKindBoolean
const FieldKindBooleanFieldKindBoolean expects a boolean.
#FieldKindOffsetDateTime
const FieldKindOffsetDateTimeFieldKindOffsetDateTime expects a date-time carrying an offset.
#FieldKindLocalDateTime
const FieldKindLocalDateTimeFieldKindLocalDateTime expects a date-time without an offset.
#FieldKindLocalDate
const FieldKindLocalDateFieldKindLocalDate expects a date.
#FieldKindLocalTime
const FieldKindLocalTimeFieldKindLocalTime expects a time of day.
#FieldKindArray
const FieldKindArrayFieldKindArray expects an array, or an array-of-tables. The element descriptor is required.
#FieldKindTable
const FieldKindTableFieldKindTable expects a table in any spelling -- a header table, an inline table, or one implied by a longer header or a dotted key. The field set is required.
#FieldKindAny
const FieldKindAnyFieldKindAny expects anything, and reports nothing about what it holds.
#WalkLeaves
const WalkLeaves WalkMode = iotaWalkLeaves visits only scalar (leaf) values. Container nodes (InlineTableNode, ArrayNode) are not passed to fn, but their children are still recursed into.
#WalkAll
const WalkAllWalkAll visits containers (inline tables, arrays) AND their children. The visitor is called for every node.
#ErrSyntax
var ErrSyntax error = kindError(KindSyntax) // a lexing or parsing failureThe kind sentinels. Match a diagnostic against one with errors.Is:
if errors.Is(err, tomledit.ErrNotFound) { ... }
Every ErrorKind has exactly one sentinel and every sentinel has exactly one kind; TestErrorKindSentinelDrift fails when one is added without the other.
#ErrUnknownKey
var ErrUnknownKey error = kindError(KindUnknownKey) // a key matching no field of the target#ErrUnknownTable
var ErrUnknownTable error = kindError(KindUnknownTable) // a table matching no field of the target#ErrMissingKey
var ErrMissingKey error = kindError(KindMissingKey) // a required key the document does not carry#ErrTypeMismatch
var ErrTypeMismatch error = kindError(KindTypeMismatch) // a value whose kind the target refuses#ErrInexact
var ErrInexact error = kindError(KindInexact) // a value the target cannot hold exactly#ErrNotFound
var ErrNotFound error = kindError(KindNotFound) // a path naming nothing#ErrBadPath
var ErrBadPath error = kindError(KindBadPath) // a syntactically invalid path#ErrWrongContainer
var ErrWrongContainer error = kindError(KindWrongContainer) // a structurally inapplicable path step#ErrBadInput
var ErrBadInput error = kindError(KindBadInput) // an invalid input to an editing operation#ErrConflict
var ErrConflict error = kindError(KindConflict) // an edit that would produce an invalid document#ErrRoundTrip
var ErrRoundTrip error = kindError(KindRoundTrip) // rendered bytes that did not survive a re-parse#ErrSkipTable
var ErrSkipTable = errors.New("skip table")ErrSkipTable is a sentinel error returned from a Walk visitor function to skip the current table's children (or inline table's children). Returning ErrSkipTable on a scalar node is a no-op.
#Cursor
type Cursor structCursor provides a fluent, nil-safe API for navigating a TOML document. A Cursor is never nil. If navigation fails at any point, the cursor captures the error and all subsequent operations (Key, At, String, etc.) become no-ops that propagate the original error. Check Err after a chain of calls to see whether the traversal succeeded.
A cursor navigates the read-layer, so Key and At step through compound tables and array-of-tables the same way a path does: Key crosses into a table however the document spells it, and At addresses an entry of an array-of-tables or an element of an array.
#Default
type Default structDefault is one entry of the EnsureDefaults input: a full PATH in this package's path syntax, and the value to seed there when the document does not carry it.
Path is a path, not a key: "server.host" reaches the "host" of the "server" table, and a key that carries a dot of its own is quoted, server."host.name". Pair is the other way round -- one key, taken verbatim -- and the two are separate types so that neither grammar can be handed to the other by accident.
#ChangeKind
type ChangeKind intChangeKind identifies the type of difference between two documents.
#Change
type Change structChange represents a single difference between two documents. OldValue is nil for Added changes; NewValue is nil for Removed changes.
#Pair
type Pair structPair is one key of an ordered inline table, the input any value-writing operation takes as a []Pair: Set, SetCreate, AppendToArray, EnsureDefaults, and the element and value positions inside them.
Key is a SINGLE key, taken verbatim -- never a path. "a.b" is one key spelled with a dot in it, written quoted, and not a table "a" holding a "b". Use a nested []Pair (or a Default's path) to reach into a table. A duplicate key and a key that is not valid UTF-8 are each refused with KindBadInput when the operation converts the value.
A map[string]any is the unordered alternative: its keys are written in sorted order, where a []Pair is written in the order given.
#ErrorKind
type ErrorKind intErrorKind classifies a diagnostic. The set is closed: every kind has exactly one matching sentinel (KindSyntax has ErrSyntax, and so on), and a diagnostic reports itself equal to the sentinel of its own kind through errors.Is.
#Error
type Error structError is the one diagnostic type of this package. Parse, edit, and access failures are all reported as Error, so a caller can match them structurally (errors.As) or by kind (errors.Is against the matching Err sentinel) instead of by message text:
var diag *tomledit.Error if errors.As(err, &diag) { fmt.Println(diag.Kind, diag.Pos.Line, diag.Pos.Column) }
Which fields are populated depends on the kind and on what the reporting site knew: Pos (line, column and byte offset) and Snippet are filled for every parse-stage diagnostic; Span only where the reporting site knows the extent of the construct it concerns, which for the parse stage means the parser's token diagnostics -- the lexer's and the duplicate-definition tracker's carry the zero Span. Path is filled for path-addressed operations, File whenever the document's origin is known (see ParseFile), and the remaining fields by the kinds documented on ErrorKind. An unpopulated field carries its zero value.
#Errors
type Errors structErrors is an aggregate of diagnostics reported together, in document order. It renders as its first diagnostic, so a call site that prints the error reads like a single failure; the whole list is reachable through errors.Unwrap semantics:
var all *tomledit.Errors if errors.As(err, &all) { for _, d := range all.Unwrap() { ... } }
errors.As with an *Error target yields the first diagnostic, and errors.Is against a kind sentinel matches when any contained diagnostic carries that kind. An empty aggregate is never returned: no diagnostics means a nil error.
#FormatConfig
type FormatConfig structFormatConfig controls how the formatter normalizes TOML output. Use DefaultFormatConfig to get sensible defaults and WithIndentWidth or WithLineWidth to override specific settings.
#FormatOption
type FormatOption func(*FormatConfig)FormatOption is a functional option for configuring the formatter.
#NodeType
type NodeType intNodeType identifies the kind of AST node.
#Node
type Node interfaceNode is the interface implemented by all AST nodes. Every node carries its original raw bytes, its trivia (whitespace and comments) and its source range. It carries no value: only the value-carrying kinds do, and those implement Scalar. Implementation is restricted to this package.
#Scalar
type Scalar interfaceScalar is the sub-interface of Node the value-carrying node kinds implement: strings, integers, floats, booleans and the four date-time flavors. Every other kind holds structure rather than a value -- a document, a table, an array-of-tables, an array, an inline table, a key, a key-value pair, a comment -- and is read through its own accessors instead.
#LocalDateTime
type LocalDateTime structLocalDateTime represents a TOML local date-time (no timezone).
#LocalDate
type LocalDate structLocalDate represents a TOML local date.
#LocalTime
type LocalTime structLocalTime represents a TOML local time.
#StringStyle
type StringStyle intStringStyle indicates the quoting style for a string node.
#IntegerBase
type IntegerBase intIntegerBase indicates the numeric base for an integer node.
#Document
type Document structDocument is the root node of a TOML document.
#TableNode
type TableNode structTableNode represents a [table] header and its children.
#ArrayTableNode
type ArrayTableNode structArrayTableNode represents an [[array-table]] header and its children.
#KeyValueNode
type KeyValueNode structKeyValueNode represents a key = value pair.
#KeyNode
type KeyNode structKeyNode represents a (possibly dotted) key.
#StringNode
type StringNode structStringNode represents a string value.
#IntegerNode
type IntegerNode structIntegerNode represents an integer value.
#FloatNode
type FloatNode structFloatNode represents a float value.
#BooleanNode
type BooleanNode structBooleanNode represents a boolean value.
#DateTimeNode
type DateTimeNode structDateTimeNode represents an offset date-time value.
#LocalDateTimeNode
type LocalDateTimeNode structLocalDateTimeNode represents a local date-time value (no timezone).
#LocalDateNode
type LocalDateNode structLocalDateNode represents a local date value.
#LocalTimeNode
type LocalTimeNode structLocalTimeNode represents a local time value.
#ArrayNode
type ArrayNode structArrayNode represents an array value.
#InlineTableNode
type InlineTableNode structInlineTableNode represents an inline table value.
#CommentNode
type CommentNode structCommentNode represents a standalone comment line.
#SegmentKind
type SegmentKind intSegmentKind distinguishes the two kinds of path step: a lookup by key and a lookup by position.
#PathSegment
type PathSegment structPathSegment is one step of a parsed path. Kind says which of the remaining fields carries the step: Key for SegmentKey, Index for SegmentIndex.
#EntryKind
type EntryKind intEntryKind classifies what a read-layer entry holds.
#Record
type Record structRecord is a table of the read-layer: an ordered set of entries, whatever the spelling that produced it. Records are read-only; edit through the document's path API.
A record is a snapshot of the document as it was when the layer was built. It stays valid until the next mutation of that document, and reading it afterwards reports what the document held before the change. Mutating a document while iterating its entries is unspecified.
#Entry
type Entry structEntry is one key of a record: the key, where it was written, and what it holds. Entries are values; holding one does not keep the document alive in any special way, and like the records they come from they are snapshots.
#Position
type Position structPosition is a location in TOML source: a 1-based line and column plus the 0-based byte offset of the same point. Columns count bytes (not runes), matching the convention used by diagnostics and tokens.
#Span
type Span structSpan is the half-open source range [Start, End) covered by a node: Start is the position of the node's first byte, End is the position immediately after its last byte.
Spans reflect the most recent Parse. Edit operations (Set, Delete, RenameKey, Merge, ...) do not update spans: nodes created programmatically carry the zero Span (IsValid reports false), and nodes whose content was edited keep the span from the last parse. To obtain fresh spans after editing, serialize with Bytes and re-Parse the result.
#FieldKind
type FieldKind intFieldKind names what a descriptor field expects. There is one member per TOML value type -- the four date-time flavors kept apart, never unified -- plus the two containers and the total FieldKindAny.
#Spec
type Spec structSpec is the descriptor of a table: the keys it may carry, and whether keys beyond them are permitted at all.
Field values are built whole and assigned into Fields -- map elements are not addressable, so mutating one in place is not a supported spelling:
spec := &tomledit.Spec{Fields: map[string]tomledit.Field{ "host": {Kind: tomledit.FieldKindString, Required: true}, "port": {Kind: tomledit.FieldKindInteger}, }}
A missing-key diagnostic is reported in lexicographic key order, because map iteration has no order to report in.
#Field
type Field structField is one expected value of a descriptor.
#WalkMode
type WalkMode intWalkMode controls which nodes the Walk visitor function is called for. Both modes traverse the syntax tree; they differ only in whether the container nodes standing between the keys and the scalars are handed to the visitor.
#Diff
func Diff(a, b *Document) []ChangeDiff returns all differences between documents a and b.
It walks both documents to collect all leaf (scalar) values, then compares them. Container nodes (inline tables, arrays) are not compared directly; instead their individual elements are compared. Changes are sorted by path (alphabetical), then by kind (Removed, Modified, Added).
The comparison reads values, never spellings. Two documents writing one value differently -- 0x2A against 42, 1_000 against 1000, one instant in two zone offsets, a literal string against a basic one, an array-of-tables against an inline array of inline tables -- report no difference. A document therefore always compares equal to itself.
Types are not bridged: an integer and a float never compare equal, so 1 and 1.0 are a modification.
#DefaultFormatConfig
func DefaultFormatConfig() FormatConfigDefaultFormatConfig returns a FormatConfig with sensible defaults.
#WithIndentWidth
func WithIndentWidth(n int) FormatOptionWithIndentWidth sets the number of spaces per indent level for values under table headers.
#WithLineWidth
func WithLineWidth(n int) FormatOptionWithLineWidth sets the maximum line width before arrays are rendered in multi-line format.
#Parse
func Parse(src []byte) (*Document, error)Parse lexes and parses TOML source bytes into a Document AST.
The returned Document preserves all whitespace, comments, and formatting from the original source. Serializing it back with Bytes produces the exact original bytes (round-trip fidelity).
Every lexing or parsing failure -- including duplicate key detection and invalid TOML syntax -- is reported as an *Error of kind KindSyntax, carrying the position, span and source line of the offending construct. Parsing stops at the first failure.
#ParseFile
func ParseFile(path string) (*Document, error)ParseFile reads the file at path and parses it. The document remembers the filename, so every diagnostic it later produces -- from parsing, access or editing -- names the file it came from.
A file that cannot be read is reported as the underlying read error (an fs.PathError, matchable with errors.Is against fs.ErrNotExist and the rest), not as an Error: nothing was parsed, so there is nothing to diagnose.
#ParsePath
func ParsePath(path string) ([]PathSegment, error)ParsePath parses a path in this package's path syntax into its segments. Every path-addressed operation in the package -- Resolve, Lookup, Set, Delete, the comment setters -- reads its path with it, so a path that parses here is spelled the way they expect.
Syntax: - A dot separates key segments: "server.host". - Brackets hold an index, which may be negative: "items[0]", "items[-1]". - Brackets may follow each other for nested arrays: "matrix[0][1]". - A quoted segment carries a key verbatim, so a key with dots or spaces stays one segment: server."host.name". The quotes belong to the path syntax, not to the key. - A backslash escapes the next byte: host\.name is the single key "host.name", and inside a quoted segment \" is a quote.
The empty path names nothing and is reported as a KindBadPath diagnostic; so are an unclosed bracket or quote, a non-numeric index, and a trailing dot.
#JoinPath
func JoinPath(segs []PathSegment) stringJoinPath renders segments as path text, and is the single quoting authority for paths: a key segment is written bare when every byte of it is legal in a bare key, and quoted (with quotes and backslashes escaped) otherwise, so that ParsePath reads back exactly the segments JoinPath was given. An index segment is written in brackets, attached to whatever precedes it.
The result of joining no segments is the empty string, which ParsePath refuses: a path names at least one step.
#QuoteString
func QuoteString(s string) stringQuoteString returns s as a TOML basic string: double-quoted, with the backslash, the double quote and the control characters escaped and everything else written verbatim.
The escapes are exactly TOML's own set. Backspace, tab, newline, form feed and carriage return take their short forms; every other control character, U+007F included, takes the four-digit "\u" form with LOWERCASE hex digits. Non-ASCII text is written as itself: a basic string carries it directly, and escaping it would only make the result harder to read.
It is TOTAL: every Go string has an output. For a string that is valid UTF-8 -- which is every string a write can carry, since the value-writing operations refuse anything else -- the result parses back to s, so QuoteString is the inverse of reading a string value. A string that is NOT valid UTF-8 is rendered with each invalid byte as U+FFFD, and the inverse property does not hold for it: no TOML spelling carries such a byte, and this renderer has no input to refuse.
#QuoteKey
func QuoteKey(s string) stringQuoteKey returns s as a TOML key: bare when TOML's bare-key rule allows it (ASCII letters, digits, hyphens and underscores, and at least one character), and the basic-string form of QuoteString otherwise. The empty key is therefore written as a pair of quotes.
It quotes ONE key. A dotted path is a sequence of keys, each quoted on its own and joined with dots; JoinPath does that for the library's path syntax.
It is total on the same terms as QuoteString, and inherits its treatment of a key that is not valid UTF-8: the invalid bytes render as U+FFFD, and the key-writing operations refuse such a key before it ever reaches here.
#FormatFloat
func FormatFloat(f float64) stringFormatFloat returns f as a TOML float. It is TOTAL: every float64 has an output, including the ones TOML has no numeric spelling for.
A finite value is written in the shortest form that reads back as the same float64, with a float marker always present -- a ".0" is appended when the shortest form carries neither a fractional part nor an exponent, so the result never reads back as an integer. Negative zero keeps its sign.
The three non-finite values are written "nan", "inf" and "-inf". A NaN with its sign bit set is written "nan" like any other: the library never writes "+nan", "-nan" or "+inf", which no reader needs and which say nothing about the value. (The value-writing operations refuse a sign-bit NaN as input rather than silently dropping the sign; this renderer, having no input to refuse, renders it.)
#FieldAny
func FieldAny() Field { return Field{Kind: FieldKindAny} }FieldAny returns the descriptor of a value of any kind: the explicit spelling of "whatever is here, I am not describing it".
#Document.GetString
func (d *Document) GetString(path string) (string, error) { return getAs[string](d, path) }GetString resolves the path and reads the value it names as a string.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not a string.
#Document.GetInt
func (d *Document) GetInt(path string) (int64, error) { return getAs[int64](d, path) }GetInt resolves the path and reads the value it names as an int64.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not an integer. A float is never an integer, however whole it is written.
#Document.GetBool
func (d *Document) GetBool(path string) (bool, error) { return getAs[bool](d, path) }GetBool resolves the path and reads the value it names as a bool.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not a boolean.
#Document.GetFloat
func (d *Document) GetFloat(path string) (float64, error) { return getAs[float64](d, path) }GetFloat resolves the path and reads the value it names as a float64: a float verbatim, and an integer the target holds exactly.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, KindTypeMismatch when the value is neither a float nor an integer, and KindInexact for an integer no float64 holds exactly.
#Document.GetTime
func (d *Document) GetTime(path string) (time.Time, error) { return getAs[time.Time](d, path) }GetTime resolves the path and reads the value it names as a time.Time: an offset date-time verbatim, and a local date-time or local date read as UTC, because a time.Time target declares that intent. A local time carries no date and is refused.
A STRING is refused with KindTypeMismatch, even one spelling a valid RFC 3339 timestamp; a struct field of type time.Time refuses one too. To read a string as a time, use GetString and parse it.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not one of the three date-time flavors above.
#Document.Children
func (n *Document) Children() []Node { return copyNodes(n.children) }Children returns the document's top-level constructs in document order, as a copy.
#TableNode.KeyPath
func (n *TableNode) KeyPath() []string { return copyStrings(n.keyPath) }KeyPath returns the parts of the header's key, decoded, as a copy.
#TableNode.Children
func (n *TableNode) Children() []Node { return copyNodes(n.children) }Children returns the constructs written under this header, in document order, as a copy.
#ArrayTableNode.KeyPath
func (n *ArrayTableNode) KeyPath() []string { return copyStrings(n.keyPath) }KeyPath returns the parts of the header's key, decoded, as a copy.
#ArrayTableNode.Children
func (n *ArrayTableNode) Children() []Node { return copyNodes(n.children) }Children returns the constructs written under this header, in document order, as a copy.
#KeyValueNode.Key
func (n *KeyValueNode) Key() *KeyNode { return n.key }Key returns the pair's key node.
#KeyValueNode.Val
func (n *KeyValueNode) Val() Node { return n.val }Val returns the pair's value node.
#KeyNode.Parts
func (n *KeyNode) Parts() []string { return copyStrings(n.parts) }Parts returns the key's parts, decoded, as a copy: "a.b" reads as ["a", "b"], and a quoted part carries no quotes.
#KeyNode.RawParts
func (n *KeyNode) RawParts() [][]byte { return copyByteSlices(n.frag.rawParts) }RawParts returns the source bytes of each part, as written, as a copy: the outer slice and every part in it. It is empty for a key created programmatically.
#KeyNode.Styles
func (n *KeyNode) Styles() []StringStyleStyles returns the quoting style of each part, as a copy.
#StringNode.Style
func (n *StringNode) Style() StringStyle { return n.style }Style returns the quoting style the string was written in.
#IntegerNode.Base
func (n *IntegerNode) Base() IntegerBase { return n.base }Base returns the numeric base the integer was written in.
#ArrayNode.Elements
func (n *ArrayNode) Elements() []Node { return copyNodes(n.elements) }Elements returns the array's elements in order, as a copy.
#InlineTableNode.Children
func (n *InlineTableNode) Children() []Node { return copyNodes(n.children) }Children returns the inline table's pairs in order, as a copy.
#CommentNode.Text
func (n *CommentNode) Text() string { return n.text }Text returns the comment line as it was written, including its "#" and its trailing newline. A comment node standing for a run of blank lines has no text of its own; Raw carries the blank bytes.
#Document.SetComment
func (d *Document) SetComment(path string, comment string) errorSetComment sets the inline comment on the node at the given path. The comment string should NOT include the "# " prefix -- it will be added automatically. An empty string removes the comment. For table paths, the comment is set on the table header line. Returns an error if the path does not exist or targets a member of an inline table (TOML forbids comments inside inline tables).
The text has to be something a comment can carry: valid UTF-8, and no control character other than a tab -- so a newline, a carriage return and U+0000 are each refused with KindBadInput, and the document is left as it was. These are the lexer's rules for reading a comment; text breaking them would render bytes the parser cannot read back.
#Document.SetLeadingComments
func (d *Document) SetLeadingComments(path string, comments []string) errorSetLeadingComments sets the leading comment lines on the node at the given path. Each string should NOT include the "# " prefix -- it will be added automatically. For table paths, the comments are set on the table header. Returns an error if the path does not exist.
Each element is ONE comment line, and each is held to what SetComment holds its text to: valid UTF-8, no control character other than a tab. An element carrying a newline would be a second line it does not get to open, so it is refused with KindBadInput -- and one refused element refuses the whole call, leaving the node's comments as they were.
A nil or empty slice removes the leading comments. An empty STRING element is not removal: it writes a "# " comment line with no content.
#Document.GetComment
func (d *Document) GetComment(path string) (string, error)GetComment returns the inline comment on the node at the given path, as text: without the "#" and the whitespace around it, so a line written x = 1 # note answers "note". A node with no inline comment answers the empty string.
The path resolves to the same node SetComment writes to -- for a key-value path the pair, not the unwrapped value; for a table path the header line -- so the two round-trip: what SetComment writes, GetComment reads back. It answers the NORMALIZED text, which is what SetComment takes, and a caller that needs the bytes as written reads Raw on the node instead.
The navigation errors are SetComment's: a path naming nothing is KindNotFound, a path naming a member of an inline table is KindWrongContainer (TOML gives an inline table nowhere to put a comment, so nothing there can carry one to read), and a malformed path is KindBadPath.
#Document.GetLeadingComments
func (d *Document) GetLeadingComments(path string) ([]string, error)GetLeadingComments returns the comment lines written above the node at the given path, in order, each as text: without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil.
The path resolves to the same node SetLeadingComments writes to, so the two round-trip, and the navigation errors are the same ones -- see GetComment.
#valueKind.String
func (k valueKind) String() stringString returns the human-readable name of the value kind.
#Document.Key
func (d *Document) Key(name string) *CursorKey returns a Cursor navigated to the named child of the document root. This is the entry point for the fluent cursor API. Chain additional Key or At calls to traverse deeper, then extract the value with String, Int, etc.
#Cursor.Key
func (c *Cursor) Key(name string) *CursorKey navigates to a named child within the current scope.
#Cursor.At
func (c *Cursor) At(index int) *CursorAt navigates to an array index. Supports negative indices.
#Cursor.Node
func (c *Cursor) Node() NodeNode returns the node at the cursor's position, or nil if the cursor has an error. A position no single node stands for -- an array-of-tables, or a table implied by a longer header or a dotted key -- has no node to return: Node reports that through Err as a KindWrongContainer diagnostic and returns nil.
#Cursor.Err
func (c *Cursor) Err() errorErr returns the first error encountered during navigation, as an *Error naming the document's file when it has one.
#Cursor.String
func (c *Cursor) String() (string, error) { return cursorAs[string](c) }String reads the value at the cursor as a string. It is deliberately not a fmt.Stringer: a cursor is a position, not a rendering of one.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not a string.
#Cursor.Int
func (c *Cursor) Int() (int64, error) { return cursorAs[int64](c) }Int reads the value at the cursor as an int64.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not an integer.
#Cursor.Bool
func (c *Cursor) Bool() (bool, error) { return cursorAs[bool](c) }Bool reads the value at the cursor as a bool.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not a boolean.
#Cursor.Float
func (c *Cursor) Float() (float64, error) { return cursorAs[float64](c) }Float reads the value at the cursor as a float64: a float verbatim, and an integer the target holds exactly.
The error is the navigation failure that ended the chain, KindTypeMismatch when the value is neither a float nor an integer, or KindInexact for an integer no float64 holds exactly.
#Cursor.Time
func (c *Cursor) Time() (time.Time, error) { return cursorAs[time.Time](c) }Time reads the value at the cursor as a time.Time: an offset date-time verbatim, and a local date-time or local date read as UTC.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not one of those three flavors -- a string among them, even one spelling a valid RFC 3339 timestamp, which a time.Time decode target refuses too.
#Document.EnsureDefaults
func (d *Document) EnsureDefaults(defaults []Default) (added []string, err error)EnsureDefaults seeds the paths the document does not already carry, in the order given, and returns the paths it added.
A path the document carries in ANY spelling is left alone: a key written as a dotted key, inside a [header] table, or inside an inline table all count as present, and so does a table that only exists because a longer header implies it. Nothing is ever overwritten and no existing value, comment or spelling is touched.
A missing intermediate table is created as a standard [header] table, never an inline one, so what the document ends up spelling depends only on the list and not on the order in which the intermediates happened to be needed: running the same list against the same document twice writes the same bytes.
Partial application: the seeding stops at the first error, everything written before it stays written, and added names exactly those paths -- so a caller that must know what it changed reads them from the return value rather than from a diff. The default that failed wrote nothing at all, not even the tables leading to it, so added is exact rather than approximate.
#ChangeKind.String
func (k ChangeKind) String() stringString returns the human-readable name of the change kind.
#Document.Set
func (d *Document) Set(path string, value any) errorSet writes a value at the given path. A key the parent does not carry yet is created there; a path whose parent does not exist is an error, which is what SetCreate relaxes.
Supported value types: string, bool, int/int8-64, uint/uint8-64, float32/64, time.Time, LocalDateTime, LocalDate, LocalTime, []any and typed slices, map[string]any (written with its keys sorted), and []Pair (written in the order given). A value is a Go value: an AST node is not one, and passing a node -- including one resolved out of this or another document -- is refused as an unsupported type. Copying a value from one key to another is a read followed by a write of the value.
A value write touches value fragments and nothing else. Where the path names a key bound by a structural construct -- a [header] table, an array-of-tables, or a table another construct only implied, by a longer header or a dotted key -- the write is refused with KindWrongContainer: those change through a structural operation, or through an explicit Delete followed by the write. A key holding an inline table is a value, and setting it replaces that value wholesale, interior comments and spellings included.
The PARENT table's spelling decides only where the write goes, never whether it happens. A table no single node stands for is written where the document already spells it out: a key it already carries is replaced through the pair that binds it; a new key of a table a dotted key spelled out joins the same region as another dotted pair ("a.b = 1" gains "a.new = 5" beside it); and a new key of a table only a longer header implies arrives under the anchoring header the write gives that table -- the one TOML allows it.
#Equality
Set is a no-op if and only if the bytes it would write for the value are exactly the bytes the value fragment already carries. Nothing else counts as equal, and nothing equal is written: the node stays, with the spelling, the lexeme and the span it was parsed with, and the document records no edit. A value the library wrote before carries no lexeme, and the comparison is against its canonical rendering. A container value -- a map, a []Pair or a slice -- is compared as a whole against the stored container's whole byte range, and replaced WHOLESALE when it differs: the interior comments and spellings of the old container do not survive, which is what setting a container value means.
One rule, no special cases: NaN spellings, infinities, signed zeros, integer-versus-float, date-time offsets, string quoting and integer bases alike. Two consequences worth stating plainly. An idempotent tool that writes values it read back normalises a non-canonical spelling the first time it touches it -- 0x2A set to 42 becomes 42 -- and is byte-stable from then on. And a same-content write over a literal-quoted string converts it to basic quoting. What you Set is what the file says.
Deciding not to write is not undoing a write: a no-op Set never clears dirtiness an earlier edit recorded.
A NaN whose sign bit is set is refused with KindBadInput, since TOML has one NaN spelling and writing it would drop the sign; the ordinary NaN is accepted and writes "nan".
Text that is not valid UTF-8 is refused with KindBadInput too, wherever it appears: a string value, a string inside a container value, a map or []Pair key, and a key the path itself names. A TOML document is UTF-8, so such bytes would be written as replacement characters and read back as something else.
#Document.SetCreate
func (d *Document) SetCreate(path string, value any) errorSetCreate is like Set but creates the intermediate tables the path names and the document does not carry, as standard [header] tables appended to the document. It refuses exactly what Set refuses, and is a no-op on exactly the same terms -- see Set's equality rule.
A refused write creates nothing. The value is converted before a single table is made, so a value the library will not write -- and a key it will not spell -- leaves the document byte-for-byte as it was, rather than leaving the headers of the path behind.
#Document.Delete
func (d *Document) Delete(path string) errorDelete removes the node at the given path from the document. It handles key-value pairs, tables, array-of-tables, and array elements.
Removal is idempotent: a path the document does not carry is a silent no-op, so an ensure-absent loop can call it unconditionally. A path that cannot be parsed, and a document the read-layer cannot fold at all, are still reported.
The spelling of the table the key sits in changes nothing about that. In a table no single node stands for -- one a dotted key spelled out, or one only a longer header implies -- the removal reaches the dotted pair that binds a value, or the headers that spell a table out, those of the tables nested inside it included.
#Document.RenameKey
func (d *Document) RenameKey(path string, newKey string) errorRenameKey changes the key name of the node at the given path to newKey.
It renames the BINDING, whatever constructs spell it out. A name bound by a value is renamed in the pair that writes it; a name bound by a table is renamed in every header that names that table, the headers of the tables nested inside it included, and in every dotted pair written under it. A name bound by an array-of-tables is renamed in every entry's header. The renamed key part is the only fragment invalidated: the brackets, the other parts, the whitespace between them and the line's comment all splice as written.
It reports KindNotFound when the path names nothing, KindWrongContainer when the last path segment is an array index (an element has no key to rename) or when the parent names an array-of-tables rather than one of its entries, KindConflict when anything in the parent already binds newKey -- a value, a table in any spelling, or an array-of-tables -- and KindBadInput when newKey is not valid UTF-8 and so is not a key TOML can carry. A refused rename changes nothing.
#Document.NewTable
func (d *Document) NewTable(path string) errorNewTable creates a new [table] header at the given path and appends it to the document. The path must consist of key segments only (no array indices).
The header must be able to bind its name. It is refused with KindConflict when anything else in the document already does: a value, an inline table, a table with its own header, an array-of-tables, or a table a dotted key implied (TOML does not allow giving one of those a header of its own). A table implied only by a LONGER header -- the "a" of an earlier [a.b] -- has no header of its own yet, and this is how it gets one. A prefix of the path that holds a value is refused on the same terms.
The refusal is about the path's FINAL key only. A table a dotted key implied may not be redefined by a header, and it may still hold sub-tables of its own: with "apple.color" written under [fruit], creating [fruit.apple] is refused and [fruit.apple.texture] is not, exactly as TOML has it.
#Document.NewArrayTable
func (d *Document) NewArrayTable(path string) errorNewArrayTable appends a new [[array-table]] entry at the given path. Multiple entries with the same path are valid in TOML and represent successive elements of the array. The path must consist of key segments only (no array indices).
A name already bound to an array-of-tables is exactly what a new entry extends. Any other binding of the name -- a value, an inline table, a table with a header of its own or one another construct implied -- is refused with KindConflict, as is a prefix of the path that holds a value.
#ErrorKind.String
func (k ErrorKind) String() stringString returns the human-readable name of the kind.
#kindError.Error
func (k kindError) Error() string { return "tomledit: " + ErrorKind(k).String() }#Error.Error
func (e *Error) Error() stringError renders the diagnostic in the compiler convention: the location, the path and the message, joined with ": ", with every absent part left out. The location is "file:line:column" when both the file and a position are known, "line:column" with a position alone, and the file alone when there is no position -- so a diagnostic reads as one of
config.toml:3:10: expected a value, got end of input 3:10: expected a value, got end of input config.toml: server.port: key not found server.port: key not found key not found
#Error.Unwrap
func (e *Error) Unwrap() error { return e.err }Unwrap returns the underlying error this diagnostic reports, or nil.
#Error.Is
func (e *Error) Is(target error) boolIs reports whether the diagnostic matches target, which is true for the kind sentinel of its own kind.
#Errors.Error
func (e *Errors) Error() stringError renders the first diagnostic of the aggregate, or "no diagnostics" when it holds none. This package never produces an empty aggregate, but the type is exported, so rendering a zero value reports rather than panics.
#Errors.Unwrap
func (e *Errors) Unwrap() []errorUnwrap returns every diagnostic of the aggregate, in document order.
#Document.Format
func (d *Document) Format(opts ...FormatOption) []byteFormat returns normalized TOML bytes. It does NOT mutate the document -- it produces a new byte slice by walking the AST and re-rendering every node with consistent formatting, ignoring all raw bytes. This is useful for enforcing a canonical style. Pass zero or more FormatOption values (e.g. WithIndentWidth, WithLineWidth) to customize the output.
The writer's blank-line grouping survives at document and table-body level: a run of blank lines becomes exactly one. The output never begins with a blank line and always ends with exactly one newline, so blank lines at either end of the document are dropped. Arrays are restructured wholesale (inline or multi-line from the configured line width), so blank lines between array elements do not survive formatting; Bytes preserves them.
The one gap the formatter opens itself is before a [table] or [[array-table]] header written flush against what precedes it: Format always inserts the missing blank line. What this library preserves is CONTENT -- the comments -- while whitespace is formatting, and Format is where the library is strict about output looking good, so the separation is not a caller's option. The insertion never removes anything and never doubles a blank line already there, so a document that separates its own tables is left as it stands.
#Cursor.Items
func (c *Cursor) Items() iter.Seq2[int, Node]Items returns a range-over-func iterator over elements of the current node. Works with ArrayNode elements and array-of-tables entries. An inert cursor (with error) yields nothing.
#Cursor.Len
func (c *Cursor) Len() intLen returns the number of elements at the current cursor position. Returns -1 if the cursor has an error or the node isn't an array/array-of-tables.
#Document.Merge
func (d *Document) Merge(other *Document) errorMerge merges all values from the other document into d. Only keys that do not exist in d are set; existing values are never overwritten. Seeding a document from a list of defaults rather than from another document is EnsureDefaults.
The other document is read through the read-layer, so what merges is what it MEANS, not how it is written: a key bound by a dotted key, by a header table or by an inline table arrives the same way, and a table the source only implies is merged as the table it implies. The target is asked the same question -- a key it spells with a longer header counts as present just as much as one with a header of its own.
Comment handling: - For existing keys: other's leading comments are appended to d's leading comments; if d has no inline comment and other does, it is copied. - For new keys: the comments of the line that binds the key travel with it -- the comments written above it and the one written after it on the same line. Comments written INSIDE a container value do not. An array or an inline table that is new in the target is written from its values, so a comment between its elements is dropped. A table merged key by key keeps the comments of every key it brings, since each of those is a binding line with comments of its own.
Two consequences of the rules above, stated so they are read as the contract and not as an accident: - Merging one source twice doubles its leading comments. The second merge finds every key already present, and the existing-key rule appends. - The tables above a new nested table are written as empty headers: a source whose only content is [deep.nest] leaves an empty [deep] header in the target above it. Merge writes through SetCreate, which spells every intermediate table the path names as a header of its own.
Array-of-tables are treated atomically: if d already has anything at a given path, all of other's entries for that path are skipped.
#StringNode.Raw
func (n *StringNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#IntegerNode.Raw
func (n *IntegerNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#FloatNode.Raw
func (n *FloatNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#BooleanNode.Raw
func (n *BooleanNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#DateTimeNode.Raw
func (n *DateTimeNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#LocalDateTimeNode.Raw
func (n *LocalDateTimeNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#LocalDateNode.Raw
func (n *LocalDateNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#LocalTimeNode.Raw
func (n *LocalTimeNode) Raw() []byte { return copyBytes(n.val.raw()) }Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
#NodeType.String
func (n NodeType) String() stringString returns the human-readable name of the node type.
#nodeBase.Raw
func (n *nodeBase) Raw() []byteRaw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
#nodeBase.Span
func (n *nodeBase) Span() SpanSpan returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
#nodeBase.Comment
func (n *nodeBase) Comment() stringComment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written x = 1 # note answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
#nodeBase.LeadingComments
func (n *nodeBase) LeadingComments() []stringLeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
#Document.Type
func (n *Document) Type() NodeType { return NodeDocument }Type returns NodeDocument.
#TableNode.Type
func (n *TableNode) Type() NodeType { return NodeTable }Type returns NodeTable.
#ArrayTableNode.Type
func (n *ArrayTableNode) Type() NodeType { return NodeArrayTable }Type returns NodeArrayTable.
#KeyValueNode.Type
func (n *KeyValueNode) Type() NodeType { return NodeKeyValue }Type returns NodeKeyValue.
#KeyNode.Type
func (n *KeyNode) Type() NodeType { return NodeKey }Type returns NodeKey.
#StringNode.Type
func (n *StringNode) Type() NodeType { return NodeString }Type returns NodeString.
#StringNode.Value
func (n *StringNode) Value() any { return n.val.get() }Value returns the decoded string.
#IntegerNode.Type
func (n *IntegerNode) Type() NodeType { return NodeInteger }Type returns NodeInteger.
#IntegerNode.Value
func (n *IntegerNode) Value() any { return n.val.get() }Value returns the integer value as int64.
#FloatNode.Type
func (n *FloatNode) Type() NodeType { return NodeFloat }Type returns NodeFloat.
#FloatNode.Value
func (n *FloatNode) Value() any { return n.val.get() }Value returns the float value as float64.
#BooleanNode.Type
func (n *BooleanNode) Type() NodeType { return NodeBoolean }Type returns NodeBoolean.
#BooleanNode.Value
func (n *BooleanNode) Value() any { return n.val.get() }Value returns the boolean value.
#DateTimeNode.Type
func (n *DateTimeNode) Type() NodeType { return NodeDateTime }Type returns NodeDateTime.
#DateTimeNode.Value
func (n *DateTimeNode) Value() any { return n.val.get() }Value returns the time.Time value.
#LocalDateTimeNode.Type
func (n *LocalDateTimeNode) Type() NodeType { return NodeLocalDateTime }Type returns NodeLocalDateTime.
#LocalDateTimeNode.Value
func (n *LocalDateTimeNode) Value() any { return n.val.get() }Value returns the LocalDateTime value.
#LocalDateNode.Type
func (n *LocalDateNode) Type() NodeType { return NodeLocalDate }Type returns NodeLocalDate.
#LocalDateNode.Value
func (n *LocalDateNode) Value() any { return n.val.get() }Value returns the LocalDate value.
#LocalTimeNode.Type
func (n *LocalTimeNode) Type() NodeType { return NodeLocalTime }Type returns NodeLocalTime.
#LocalTimeNode.Value
func (n *LocalTimeNode) Value() any { return n.val.get() }Value returns the LocalTime value.
#ArrayNode.Type
func (n *ArrayNode) Type() NodeType { return NodeArray }Type returns NodeArray.
#InlineTableNode.Type
func (n *InlineTableNode) Type() NodeType { return NodeInlineTable }Type returns NodeInlineTable.
#CommentNode.Type
func (n *CommentNode) Type() NodeType { return NodeComment }Type returns NodeComment.
#EntryKind.String
func (k EntryKind) String() stringString returns the human-readable name of the entry kind.
#Document.Root
func (d *Document) Root() *RecordRoot returns the read-layer view of the document: the record holding every top-level key, in first-appearance order.
Parsing folds nothing; the layer is built the first time a logical question is asked and kept until the document is written to, so repeated reads share one fold and reads of a shared document stay safe under concurrency. A write drops it: the next call folds again and answers what the document says then. A record obtained BEFORE a write keeps answering what the document said before it -- layer handles are snapshots, and a stale one reports stale data rather than reporting an error. Mutating a document while iterating its entries is unspecified.
Root panics when the document cannot be folded -- when two constructs claim one key, for instance. A parsed document never can be: the parser refuses every such conflict. Only an editing sequence that built an invalid document can reach it.
#Record.Len
func (r *Record) Len() int { return len(r.entries) }Len returns the number of entries in the record.
#Record.Span
func (r *Record) Span() Span { return r.span }Span returns the record's anchoring range: its own header where it has one, the inline table that spells it out, or the construct that implied it.
#Record.Node
func (r *Record) Node() (Node, bool)Node returns the concrete node backing the record, and whether one does. A table written as a header or as an inline table is backed by that construct, an array-of-tables entry by its own [[header]], and the root record by the document. A record no single node stands for -- one implied by a longer header or by a dotted key -- reports false; it exists in the layer, and nothing in the source stands for it alone.
#Record.Entries
func (r *Record) Entries() iter.Seq[Entry]Entries iterates the record's entries in first-appearance order.
#Record.Get
func (r *Record) Get(key string) (Entry, bool)Get returns the entry with the given key, and whether the record has one.
#Entry.Key
func (e Entry) Key() string { return e.key }Key returns the entry's key, decoded (quotes and escapes resolved).
#Entry.KeySpan
func (e Entry) KeySpan() Span { return e.keySpan }KeySpan returns the source range of the key itself -- the one part of the construct the entry is anchored to that names it.
#Entry.Kind
func (e Entry) Kind() EntryKind { return e.kind }Kind reports what the entry holds.
#Entry.Record
func (e Entry) Record() (*Record, bool)Record returns the record this entry holds, for an entry of kind EntryRecord: a table in any spelling. It reports false for any other kind.
#Entry.Records
func (e Entry) Records() ([]*Record, bool)Records returns the entries of the array-of-tables this entry holds, for an entry of kind EntryRecords. The returned slice is a copy: appending to it or reordering it changes nothing in the document. It reports false for any other kind.
#Entry.RecordsSpan
func (e Entry) RecordsSpan() Span { return e.recordsSpan }RecordsSpan returns the synthesized range of an array-of-tables: from the first entry's header to the end of the last entry's content, or to the end of its header when that entry has no content of its own. It is the zero Span for an entry of any other kind -- no concrete node covers a collection, so this is the only range that describes one.
#Entry.Node
func (e Entry) Node() (Node, bool)Node returns the value node the entry holds, and whether it holds one. It answers for entries of kind EntryValue -- a scalar or a plain array -- and reports false for every other kind: a record's own backing construct is Record.Node's answer, and an array-of-tables has no single node at all, only the entries Records returns.
#Document.Bytes
func (d *Document) Bytes() []byteBytes serializes the document back to TOML bytes.
A document parsed and never edited renders as the exact bytes it was parsed from. An edited one renders per FRAGMENT: every byte range an edit did not touch is written back as it was read, and only the ranges the edits invalidated are rendered anew, in the library's canonical spellings.
So a value write leaves the line's spacing, its inline comment and the quoting of its key exactly as they were; a comment write leaves the value's base and quoting; a write inside an array or an inline table leaves every sibling, separator and interior comment; and a rename leaves everything but the renamed key part. What the library writes -- and only that -- is written in the forms of QuoteString, QuoteKey and FormatFloat.
#Document.Resolve
func (d *Document) Resolve(path string) (Node, error)Resolve resolves the path against the document and returns the node it names. For a key-value pair the value node is returned, not the pair.
Path syntax is ParsePath's: dots between keys ("server.host"), brackets for indices ("items[0]", "items[-1]" for the last), quotes around a key that would otherwise read as more than one segment.
The returned *Error carries KindBadPath for a syntactically invalid path, KindNotFound for a path naming nothing, and KindWrongContainer for a step that does not apply to what it addresses -- including a path that names something no single node stands for: an array-of-tables (index it, or read its entries through Root) or a table implied by a longer header or a dotted key. Lookup and Has answer the same question without an error.
#Document.Lookup
func (d *Document) Lookup(path string) (Node, bool)Lookup returns the node the path names, and whether it names one. It is the comma-ok form of Resolve, and answers about CONCRETE nodes: a path naming something no single node stands for -- an array-of-tables, or a table implied by a longer header or a dotted key -- reports false, even though the read-layer carries it. Use Root to read those.
#Document.Has
func (d *Document) Has(path string) boolHas reports whether the path names a concrete node, on the same terms as Lookup.
#StringNode.AsString
func (n *StringNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#StringNode.AsInt
func (n *StringNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#StringNode.AsFloat
func (n *StringNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#StringNode.AsBool
func (n *StringNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#StringNode.AsTime
func (n *StringNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#StringNode.AsLocalDateTime
func (n *StringNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#StringNode.AsLocalDate
func (n *StringNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#StringNode.AsLocalTime
func (n *StringNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#IntegerNode.AsString
func (n *IntegerNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#IntegerNode.AsInt
func (n *IntegerNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#IntegerNode.AsFloat
func (n *IntegerNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#IntegerNode.AsBool
func (n *IntegerNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#IntegerNode.AsTime
func (n *IntegerNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#IntegerNode.AsLocalDateTime
func (n *IntegerNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#IntegerNode.AsLocalDate
func (n *IntegerNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#IntegerNode.AsLocalTime
func (n *IntegerNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#FloatNode.AsString
func (n *FloatNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#FloatNode.AsInt
func (n *FloatNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#FloatNode.AsFloat
func (n *FloatNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#FloatNode.AsBool
func (n *FloatNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#FloatNode.AsTime
func (n *FloatNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#FloatNode.AsLocalDateTime
func (n *FloatNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#FloatNode.AsLocalDate
func (n *FloatNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#FloatNode.AsLocalTime
func (n *FloatNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#BooleanNode.AsString
func (n *BooleanNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#BooleanNode.AsInt
func (n *BooleanNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#BooleanNode.AsFloat
func (n *BooleanNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#BooleanNode.AsBool
func (n *BooleanNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#BooleanNode.AsTime
func (n *BooleanNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#BooleanNode.AsLocalDateTime
func (n *BooleanNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#BooleanNode.AsLocalDate
func (n *BooleanNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#BooleanNode.AsLocalTime
func (n *BooleanNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#DateTimeNode.AsString
func (n *DateTimeNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#DateTimeNode.AsInt
func (n *DateTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#DateTimeNode.AsFloat
func (n *DateTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#DateTimeNode.AsBool
func (n *DateTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#DateTimeNode.AsTime
func (n *DateTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#DateTimeNode.AsLocalDateTime
func (n *DateTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#DateTimeNode.AsLocalDate
func (n *DateTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#DateTimeNode.AsLocalTime
func (n *DateTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#LocalDateTimeNode.AsString
func (n *LocalDateTimeNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#LocalDateTimeNode.AsInt
func (n *LocalDateTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#LocalDateTimeNode.AsFloat
func (n *LocalDateTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#LocalDateTimeNode.AsBool
func (n *LocalDateTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#LocalDateTimeNode.AsTime
func (n *LocalDateTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#LocalDateTimeNode.AsLocalDateTime
func (n *LocalDateTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#LocalDateTimeNode.AsLocalDate
func (n *LocalDateTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#LocalDateTimeNode.AsLocalTime
func (n *LocalDateTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#LocalDateNode.AsString
func (n *LocalDateNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#LocalDateNode.AsInt
func (n *LocalDateNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#LocalDateNode.AsFloat
func (n *LocalDateNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#LocalDateNode.AsBool
func (n *LocalDateNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#LocalDateNode.AsTime
func (n *LocalDateNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#LocalDateNode.AsLocalDateTime
func (n *LocalDateNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#LocalDateNode.AsLocalDate
func (n *LocalDateNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#LocalDateNode.AsLocalTime
func (n *LocalDateNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#LocalTimeNode.AsString
func (n *LocalTimeNode) AsString() (string, error) { return nodeAs[string](n) }AsString reads the node through the conversion table.
#LocalTimeNode.AsInt
func (n *LocalTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }AsInt reads the node through the conversion table.
#LocalTimeNode.AsFloat
func (n *LocalTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }AsFloat reads the node through the conversion table.
#LocalTimeNode.AsBool
func (n *LocalTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }AsBool reads the node through the conversion table.
#LocalTimeNode.AsTime
func (n *LocalTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }AsTime reads the node through the conversion table.
#LocalTimeNode.AsLocalDateTime
func (n *LocalTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }AsLocalDateTime reads the node through the conversion table.
#LocalTimeNode.AsLocalDate
func (n *LocalTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }AsLocalDate reads the node through the conversion table.
#LocalTimeNode.AsLocalTime
func (n *LocalTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }AsLocalTime reads the node through the conversion table.
#Position.IsValid
func (p Position) IsValid() bool { return p.Line >= 1 }IsValid reports whether the position was populated from source (Line >= 1). The zero Position is invalid.
#Span.IsValid
func (s Span) IsValid() bool { return s.Start.IsValid() }IsValid reports whether the span was populated by Parse. The zero Span is invalid; it is returned by nodes created programmatically by an edit.
#FieldKind.String
func (k FieldKind) String() stringString returns the human-readable name of the field kind.
#Document.Validate
func (d *Document) Validate(spec *Spec) errorValidate checks the document against the descriptor and reports every independent violation it finds, in document order, as one aggregate error (see Errors): an unknown key or table, a value of a kind the field refuses, a value the field cannot hold exactly, and a required key the document does not carry. Validation continues across siblings but never descends below a construct it has already refused, so one broken table cannot bury the document in diagnostics from its interior.
A descriptor that does not describe anything -- an array field with no Elem, a table field with no Table, a sub-descriptor on a kind that has no use for one -- is refused before the document is looked at.
Validate reports nil when the document satisfies the descriptor.
#Document.DecodeSpec
func (d *Document) DecodeSpec(spec *Spec) (map[string]any, error)DecodeSpec checks the document against the descriptor and, when it satisfies it, returns the document's values as native Go data: map[string]any for every table spelling, []any for arrays and arrays of tables, and string, int64, float64, bool, time.Time, LocalDateTime, LocalDate or LocalTime for scalars.
It is the descriptor path's decode: the same engine Validate runs, followed by a value built out of the read-layer. No reflection is involved and no consumer code runs, so the result is exactly what the document says.
DecodeSpec is ATOMIC, without exception: it returns a map only when the document has no violations at all, and (nil, err) otherwise. There is no partial map, no half-populated table, and nothing to inspect after an error -- the diagnostics are the whole answer. This is a stronger promise than the typed entry points make, and it holds because nothing outside this package participates in building the map.
The errors are Validate's: an *Errors aggregate of every independent violation in document order, or a plain error for a descriptor that describes nothing.
#Document.PermuteChildren
func (d *Document) PermuteChildren(path string, order []int) errorPermuteChildren reorders the children of the container the path names. The empty path addresses the document's own children.
The order is a GATHER: order[i] is the index of the child that moves to position i, so children [A, B] with order [1, 0] end up [B, A]. It must be total -- a permutation of every index the container has, each exactly once. A wrong length, an out-of-range index and a repeated one are each refused with KindBadInput, and nothing is reordered. The two index violations name the offending index; a length mismatch is about the order as a whole and names both counts instead.
The indices address the container's children as they stand right now. Read them, compute the order and permute in one editing sequence: an edit in between that adds or removes a child shifts every index after it, and the permutation would then move the wrong nodes.
A child's own trivia travels with it -- its leading comments, its inline comment and the blank lines before it are part of the child, not of the position it used to hold.
#Document.AppendToArray
func (d *Document) AppendToArray(path string, value any) errorAppendToArray appends a value to the array the path names.
The value is converted the way Set converts one: the Go types listed there, including []Pair for an ordered inline table. An array-of-tables is not an array value -- add an entry to one with NewArrayTable.
#Document.RemoveFromArray
func (d *Document) RemoveFromArray(path string, index int) errorRemoveFromArray removes the element at index from the array the path names. A negative index counts from the end, so -1 removes the last element.
Unlike Delete, which is idempotent by contract, this operation names a position that must exist: an index outside the array is refused with KindNotFound.
#tokenType.String
func (t tokenType) String() stringString returns the internal name of the token type. It is the lexer's own vocabulary, for test failures and debugging output; a diagnostic a caller reads renders describe instead.
#Document.Walk
func (d *Document) Walk(fn func(path string, node Node) error, mode WalkMode) errorWalk is the SYNTACTIC traversal: it walks the syntax tree, in the order the file writes it, and hands the visitor the concrete nodes it finds -- each with the spelling, the trivia and the span it was written with. A value written as an inline table arrives as an *InlineTableNode and one written under a header arrives as the header's children, because that is what the two files contain.
The LOGICAL traversal is the read-layer: Root returns the document's records and entries with the spellings folded away, and a consumer walks it by recursing over Record.Entries. Use Walk to ask what the file contains and how it is written; use the read-layer to ask what the document means.
Walk visits every key-value pair in the document in order, calling fn with the dot-path and the value node. Tables and array-of-tables are walked into (their children are visited), not yielded as standalone entries. Inline tables and arrays are yielded first, then their children are recursed into. Comment nodes are not visited: a comment reaches the visitor as the trivia of the node it is attached to.
The mode parameter controls which nodes are visited: - WalkLeaves: only scalar values (containers are recursed but not yielded) - WalkAll: containers AND their children are yielded
The path uses dot-separated keys with bracket indices for array-of-tables entries (e.g. "servers[0].host"). Return ErrSkipTable from fn to skip the children of the current inline table or array. Return any other non-nil error to stop the walk immediately.
#Document.WriteFile
func (d *Document) WriteFile(path string) errorWriteFile renders the document and writes it to path, atomically, and only after the rendered bytes have proved they survive a round trip.
The write is atomic in the sense a rename gives: the bytes go to a temporary file in the DESTINATION'S OWN DIRECTORY -- so the rename cannot cross a filesystem boundary -- and that file replaces the destination in one step. A failure anywhere before the rename leaves the destination exactly as it was and no temporary file behind. (A machine that loses power mid-write is the kernel's business, not this function's.)
The round trip is checked before anything is written: the rendered bytes must parse, and re-rendering that parse must reproduce them byte for byte. A failure is an *Error of kind KindRoundTrip whose Offset field carries the byte at which the two disagree -- or, when the rendered bytes do not parse at all, the offset at which they stopped being TOML, with the parse error wrapped so errors.Is still reaches it. Nothing is written in either case.
The destination's file mode is preserved when it already exists. A new file is created with mode 0o644, as an ordinary create would, so the process umask applies to it.
A filesystem failure is reported as the underlying error -- an fs.PathError, matchable with errors.Is -- and not as an Error, on the same terms as ParseFile: nothing about the document is wrong, so there is nothing to diagnose about it.