34 KiB
API
The library exports the surface below from the sourcedock.dev/petrbalvin/interpres/v2
package. The snippets assume:
import "sourcedock.dev/petrbalvin/interpres/v2"
The parser implements TOML 1.1: date-times and times without seconds, the
\e and \xHH escape sequences, and multi-line inline tables with comments
and trailing commas. The encoder emits TOML 1.1.
Functions
func Parse(data []byte) (*Document, error)
Decodes a TOML document into a Document: the values, the order the
keys were written in, whether a table was written inline, and the comments.
The values follow the mapping in the Decoding section below.
Returns *SyntaxError on a malformed document. Input that is not valid UTF-8
is rejected with a SyntaxError naming the line where the invalid byte
appears, because validity is checked during the scan. Equivalent to
ParseContext(context.Background(), data).
doc, err := interpres.Parse([]byte("title = \"x\"\nport = 8080\n"))
tree := doc.Map()
func ParseContext(ctx context.Context, data []byte) (*Document, error)
The cancellable variant of Parse. An already-cancelled context returns
ctx.Err() before any work. During parsing the context is checked every 64
top-level statements, so a long document aborts without running to completion.
func ParseMap(data []byte) (map[string]any, error)
Decodes a TOML document into an untyped tree, the shape this package parsed
into before Document existed: the order of the keys and the
comments are not part of a map, so they are dropped. Use it when only the
values matter, or when the extra bookkeeping of a document is not wanted.
Equivalent to ParseMapContext(context.Background(), data).
tree, err := interpres.ParseMap([]byte("title = \"x\"\nport = 8080\n"))
func ParseMapContext(ctx context.Context, data []byte) (map[string]any, error)
The cancellable variant of ParseMap.
func ParseFile(path string) (*Document, error)
Reads the file at path and parses it into a Document, the shape
Parse gives. Both a read failure and a parse failure come back with the file
name as their first words, wrapped so errors.AsType still reaches the
SyntaxError inside a parse failure.
doc, err := interpres.ParseFile("config.toml")
func Valid(data []byte) error
Reports whether data is a valid TOML document: nil when the parser accepts
it, the parse error when it does not. It is the library call the -validate
mode of interpres-decode is built on.
if err := interpres.Valid(data); err != nil {
fmt.Println("invalid:", err)
}
func UnmarshalWithOptions(data []byte, v any, opts DecodeOptions) error
The one-shot form of a configured Decoder: the same options as
NewDecoder sets, in a DecodeOptions struct, applied to a single call.
The zero value takes the defaults: unknown keys ignored, numbers evaluated
as int64 and float64, no size limit and the 10000-level nesting default.
Documents
Parse returns a Document: the value tree together with what a map cannot
carry, which is the order the keys were written in, whether a table was written
as an inline table or under a header, and the comments. ParseMap gives the
plain tree when none of that is wanted.
doc, err := interpres.Parse(data)
if err != nil {
return err
}
root := doc.Root()
for _, key := range root.Keys() { // written order, not sorted
entry, _ := root.Get(key)
fmt.Println(key, entry.Value())
}
The values are shared with the tree ParseMap returns, so a value read from a
document and from doc.Map() is the same value.
| Type | Meaning |
|---|---|
Document |
the parsed document: Root() for the top-level table, Map() for the value tree, Footer() for a comment block at the end |
Table |
one TOML table: Keys() and Entries() in written order, Get(key), Values() for its part of the value tree, Inline() |
Entry |
one key: Value(), Inline(), Table() when the value is a table, Elements() for the tables of an array value |
Elements() holds one node per element of an array value: the tables of an
array of tables, and the inline tables inside a value array, with nil for the
elements that are not tables.
Comments
A comment belongs to the line it precedes or follows, and to the node that line introduced:
| Written | Carried by |
|---|---|
| lines above a key | that key's Entry, through Comments() |
| a comment beside a key | that key's Entry, through Trailing() |
lines above a [header] or [[header]] |
that Table, through Comments() |
| a comment beside a header | that Table, through Trailing() |
| a comment block after the last statement | the Document, through Footer() |
SetComments and SetTrailing replace them. A line carries no leading #
and no surrounding space, so # note is stored as note and a bare # as
"".
A Document is not a value to marshal: Marshal writes values, so it refuses
one and points at doc.Map(). Writing a document back, with its order and its
comments, belongs with the editing API.
func Unmarshal(data []byte, v any) error
Parses data and stores the result in the value pointed to by v, typically a
pointer to a struct or to map[string]any. Equivalent to
UnmarshalContext(context.Background(), data, v).
var cfg Config
if err := interpres.Unmarshal(data, &cfg); err != nil {
return err
}
func UnmarshalContext(ctx context.Context, data []byte, v any) error
The cancellable variant of Unmarshal.
func Marshal(v any) ([]byte, error)
Encodes a struct or map[string]V value, or a non-nil pointer to one, into a
TOML document. The emission rules are in the Encoding section
below. Equivalent to MarshalContext(context.Background(), v).
out, err := interpres.Marshal(cfg)
func MarshalContext(ctx context.Context, v any) ([]byte, error)
The cancellable variant of Marshal. The context is checked before any work
and every 64 fields during the reflection walk.
func MarshalAppend(buf []byte, v any) ([]byte, error)
Appends the TOML encoding of v to buf and returns the extended buffer, the
shape json.MarshalAppend has. A failed encoding leaves buf untouched.
Decoding
Value mapping
Parse and Unmarshal map TOML values to Go types as follows:
| TOML value | Go type in the parsed tree |
|---|---|
| string | string |
| integer | int64 |
| float | float64 |
| boolean | bool |
| offset date-time | OffsetDateTime |
| local date-time | LocalDateTime |
| local date | LocalDate |
| local time | LocalTime |
| array | []any |
| table, inline table | map[string]any |
| array of tables | []map[string]any |
When decoding into a struct, these values convert onto the destination's
concrete types: any integer or unsigned width, floats, slices, nested structs
and map[string]T. Decoder.UseNumber replaces the two numeric rows of the
table with Number, which keeps the literal; see
Numbers as literals.
Target constraints
Unmarshal and (*Decoder).Decode write into a non-nil pointer:
*struct, matched per the field rules below*map[string]anyor*map[string]T, keys become map keys and values decode intoTrecursively; a map that already holds entries is merged into, the document's values replacing same-named keys and the rest left standing*OrderedMap, the keys fill in the order the document wrote them; see Ordered tables*any, receives the whole parsed tree unchanged
Anything else returns interpres: decode target must be a non-nil pointer.
Field matching
For a struct destination, a TOML key matches a field as follows:
- The
toml:"name"tag, using the part before any comma. The literal-excludes the field. - Without a tag, the lower-cased field name.
- An anonymous (embedded) field without a tag is inlined: the decoder walks into the embedded struct and matches its own fields against the same keys, mirroring how the encoder flattens it. A nil embedded pointer struct is allocated on demand. An untagged embedded map receives the keys no field claims; when a struct embeds several untagged maps, the first one declared takes all of them and the rest stay untouched, so the rule stays predictable.
- The key itself is lower-cased before lookup, so the match is
case-insensitive on both sides:
DATABASEURLmatches a field namedDatabaseUrl.
The match is exact after lower-casing. No separator is inserted, so a TOML key
database_url does not match a field named DatabaseUrl; tag such a field
(toml:"database_url") or use the lower-cased name as the key. When two
fields resolve to the same name, the shallower one wins; at equal depth, the
one declared later wins.
Unknown keys are ignored by default, landing in an untagged embedded map when the struct has one; Strict decoding rejects them instead.
The tag may carry the required option, toml:"host,required": the decode
fails with missing required key "host" when no key of the document resolved
to the field. The check runs after the table is read, so the other fields
carry their values whether the required one is present or not.
Numeric conversion
The parser produces int64 for every integer and float64 for every float.
The decoder converts to the destination type with explicit overflow checks:
| Destination kind | Rule |
|---|---|
int, int8, int16, int32, int64 |
the int64 value must not overflow the destination |
uint, uint8, uint16, uint32, uint64 |
the value must be non-negative and must not overflow the destination's own width, uint on a 32-bit platform included; uint64 accepts any non-negative int64 |
float32, float64 |
copied verbatim, except that a finite value beyond the float32 range is an overflow error rather than a silent infinity; an integer also coerces, so TOML 5 decodes into 5.0 |
bool, string |
exact kind match only, no coercion across kinds |
time.Time, OffsetDateTime |
offset date-times only; no implicit conversion to or from the local variants |
A conversion that the rules do not allow produces an error wrapped with the
offending key or index, for example p: interpres: integer 300 overflows uint8.
Numbers as literals
NewDecoder().UseNumber() decodes every integer and float into Number, a
string type that carries the literal the document wrote: 0x1f, 1_000,
+1.0, inf. The shape is validated as strictly as ever, so 01 and 1__0
remain parse errors; only the evaluated value is replaced by the literal. A
round trip through the value tree and Marshal keeps the spelling, where the
default tree normalises 0x1f to 31 and +1.0 to 1.0.
var tree map[string]any
err := interpres.NewDecoder().UseNumber().Decode(data, &tree)
lit := tree["rate"].(interpres.Number) // "1_000"
A destination of a concrete kind is unaffected: an int64 field, a float64
field and a time.Duration field take the evaluated value they always took,
and a Number field takes the literal. Number.Float64 and Number.Int64
evaluate the literal on demand, with an error for a float asked as an integer
and for a literal that is not a valid TOML number. Marshal writes a Number
as its bare literal and rejects one that is not a valid TOML number, whether it
stands alone or inside a value array.
Date-time values
Offset date-times decode into OffsetDateTime, whose embedded time.Time is the
instant with the offset the document wrote; a destination of the plain
time.Time takes the same value, so a timestamp field does not have to name the
wrapper. The local variants decode into LocalDateTime, LocalDate and
LocalTime, whose embedded time.Time is normalised to UTC (midnight UTC for a
local date, the zero date for a local time). Every kind may omit the seconds as
of TOML 1.1 (07:32, 1979-05-27T07:32); such a value carries a zero second,
and the encoder writes the seconds only when the value carries them, so a
document written without seconds comes back without them. There is no implicit
conversion between the offset and local kinds; assigning one to the other is an
error. The date-time types take a bare timestamp and never a quoted string, so a
document that writes a date-time with quotes does not decode into them, and
neither encoding.TextUnmarshaler nor the embedded time.Time changes that.
Arrays of tables
A [[a]] block parses into a []map[string]any element of the tree. When the
destination is a slice, each element decodes into the slice's element type
([]struct or []map[string]V); a mismatch on one element surfaces as an
error wrapped with [i]: and the element index.
A value array also decodes into a fixed-size array, [N]T, the mirror of the
encoder's ability to encode one. The element count has to match: an array
whose length differs from N is an error, interpres: cannot assign 2 elements to [3]int, wrapped with the key path.
Custom decoding: Unmarshaler
A type that wants full control of its decode implements:
type Unmarshaler interface {
UnmarshalTOML(data any) error
}
data is whatever the parser produced for that key: string, bool, int64,
float64, OffsetDateTime, LocalDateTime, LocalDate, LocalTime, []any, or
map[string]any. The method inspects the value and mutates its own receiver;
the decoder keeps whatever state the receiver stored.
The method is usually on a pointer receiver (*T). The decoder invokes it when
the destination type or its pointer implements the interface, so a
pointer-receiver implementation on an addressable struct field is found
automatically, and a nil pointer destination is allocated first. An error
returned from UnmarshalTOML halts the decode and propagates wrapped with the
key path, for example addr: unmarshal: not a string.
Custom decoding: UnmarshalerContext
UnmarshalerContext is Unmarshaler with the decode's context handed in:
type UnmarshalerContext interface {
UnmarshalTOMLContext(ctx context.Context, data any) error
}
A type that implements both gets UnmarshalTOMLContext, so a long custom
decode can abort on cancellation instead of running to completion. The
context a non-cancellable entry point carries is context.Background, never
nil.
Custom decoding: encoding.TextUnmarshaler
A destination type that implements encoding.TextUnmarshaler receives a TOML
string as its text content, the rule encoding/json follows:
func (ip *IP) UnmarshalText(text []byte) error
The decoder looks for the method on the destination and on its address, so a
pointer-receiver UnmarshalText is invoked on an addressable struct field, and
the elements of a slice destination are reached the same way. The text path
applies to TOML strings only: every other value kind keeps its own rule, so
r = 1 does not reach a receiver that expects text. An error from
UnmarshalText halts the decode and propagates with the key path and the
prefix unmarshal text:, for example addr: unmarshal text: not an address.
UnmarshalTOML wins over UnmarshalText when
a type implements both, and the four date-time
types are excluded: a quoted string stays a string and
never becomes an OffsetDateTime or one of the local wrappers.
Durations
TOML has no duration type, so time.Duration has a rule of its own. The
encoder writes the canonical Go form in a TOML string, 1h30m0s, and the
decoder reads that string back with time.ParseDuration. A bare integer is
still the nanosecond count it has always been, so from_int = 5400000000000
and from_text = "1h30m" decode to the same duration. Text that
time.ParseDuration rejects, d = "90" among it, fails with
interpres: invalid duration "90".
Ordered tables
OrderedMap is a string-keyed table that remembers the order its keys were
set in, the shape a map[string]any cannot carry. Decoding into one fills it
in the order the document wrote the keys, and Marshal writes one back in
that order, where a map destination carries no order and a map source sorts
its keys. The type is a decode target on its own, in a struct field, and as
the element of an array of tables.
var cfg OrderedMap
err := interpres.Unmarshal(data, &cfg)
out, err := interpres.Marshal(&cfg) // the keys come back in written order
The values are untyped, the shape the parser produces, so a nested table
inside an OrderedMap is a plain map[string]any; the order is kept at the
level the OrderedMap sits at. Inside a value array an OrderedMap renders
as an ordinary inline table, whose keys are sorted.
Strict decoding
By default unknown keys are dropped silently. A Decoder built with
DisallowUnknownFields rejects them instead:
err := interpres.NewDecoder().
DisallowUnknownFields().
Decode(data, &cfg)
A typo such as database_urls then fails with
interpres: unknown field "database_urls" for main.Config instead of a silent
default-zero run. Strictness applies to every struct the decode reaches, at any
depth, including struct elements inside slices; map destinations accept every
key by nature. When several keys are unknown, the message names the smallest
one, so it does not depend on map iteration order.
Cancellation
ParseContext, UnmarshalContext and (*Decoder).DecodeContext accept a
context.Context. An already-cancelled context short-circuits with
context.Canceled before any work begins; afterwards the context is checked
every 64 top-level statements.
Flow
sequenceDiagram
participant Caller
participant Unmarshal as Unmarshal
participant Parser as parser
participant Decoder as decoder
Caller->>Unmarshal: data, v
Unmarshal->>Parser: ParseContext(ctx, data)
Parser-->>Unmarshal: tree or *SyntaxError
Unmarshal->>Decoder: decode(tree, reflect value)
Decoder-->>Unmarshal: nil or wrapped field error
Unmarshal-->>Caller: error
Encoding
Input constraints
Marshal and (*Encoder).Marshal accept a struct, a map[string]V, or a
non-nil pointer to one, where V is any value Marshal itself understands. A
different top-level value fails:
| Input | Error |
|---|---|
| a bare scalar or array | interpres: top-level value must be a struct or map[string]V, got <type> |
a nil any |
interpres: cannot marshal nil value |
| a nil pointer | interpres: cannot marshal nil pointer |
The encoding walk carries a nesting limit of 10000 levels, the parser's own figure: a value that nests deeper, which cyclic data always does, is rejected with an error that names the limit and suggests the cycle, instead of running the stack out.
Field matching
Struct fields become TOML keys as follows:
- The
toml:"name"tag, using the part before any comma. The literal-skips the field. - Without a tag, the lower-cased field name. The key emitted for a field named
DatabaseUrlisdatabaseurl; tag the field to emitdatabase_url. - An anonymous (embedded) field without a tag is inlined into the parent table; with a tag it is a regular field under that name.
Keys that match [A-Za-z0-9_-]+ are emitted bare, all others quoted. A
map[string]V emits its keys in sorted order for deterministic output, and a
nil map emits nothing.
Tag options
The part of a toml tag after the first comma carries options. Both options
shape emission only; the decoder ignores them.
omitzeroskips the field when its value is the zero value of its type. A type with anIsZero() boolmethod (time.Time among them) decides through that method, so a zerotime.Timeor an all-zero struct disappears from the output.omitemptyskips the field when it holds an empty collection: a nil or empty slice or array, or a nil or empty map. Strings and other scalars are not covered byomitempty; useomitzerofor those.
type Config struct {
Host string `toml:"host,omitzero"`
Started time.Time `toml:"started,omitzero"`
Tags []string `toml:"tags,omitempty"`
}
Options combine after the name: toml:"name,omitempty,omitzero" is valid, and
an unknown option is ignored.
Untagged embedded fields round-trip: the decoder inlines embedded structs and routes unclaimed keys into an embedded map exactly where the encoder flattened them.
Group-by-kind layout
By default every table is emitted with its entries grouped by kind:
- scalars (
string,int64,float64,bool,time.Time,OffsetDateTime,LocalDateTime,LocalDate,LocalTime) - sub-tables (structs and
map[string]Vvalues) - arrays of tables (
[]structand[]map[string]V)
Within each group the order follows struct field declaration order, or sorted
key order for maps. This is the only layout that reliably re-parses to the same
tree: once a [header] is written, later scalars at the parent level would be
parsed as keys of the sub-table.
Preserving declaration order
GroupByKind(false) on an Encoder walks the entries in declaration order
instead, emitting each header immediately before its content:
out, err := interpres.NewEncoder().GroupByKind(false).Marshal(cfg)
The output remains parseable, but a scalar declared after a sub-table lands under that sub-table's header when the document is read back. Use this layout for presentation only, not when the output must round-trip.
Custom encoding: Marshaler
A type that wants a non-default TOML shape implements:
type Marshaler interface {
MarshalTOML() (any, error)
}
The returned value is encoded as if it had been passed in place of the
receiver, so it may be a scalar, a slice, an array of tables, or another
struct or map, including the Marshaler result of another type; the encoder
recurses, and the result is normalised like any other value, so a method may
return a plain int or a time.Duration.
An error returned from MarshalTOML fails the marshal wrapped with the key
path, for example interpres: server.port: bad timestamp. A result of nil with
a nil error fails the same way with MarshalTOML returned a nil value: nil has
no TOML representation, so dropping the field silently is not an option.
The method is reached for every value the walk meets, array elements included:
an element that renders itself as a table keeps the [[header]] form, one that
renders itself as a scalar turns the array into a value array, and the method
runs once per element. It is looked up on the value and on its address, so a
pointer-receiver method is called for a field or an element, exactly as
MarshalText is.
type Port int
func (p Port) MarshalTOML() (any, error) {
return int64(p), nil
}
Custom encoding: encoding.TextMarshaler
A type that implements encoding.TextMarshaler is encoded as a TOML string
holding the text the method returns, which is the rule encoding/json follows:
func (ip IP) MarshalText() ([]byte, error)
The encoder looks for the method on the value and on its address, so a
pointer-receiver MarshalText is found on a struct field of an addressable
value (pass a pointer to Marshal) and always on a slice element. net.IP,
netip.Addr and user types follow this rule, and a struct that implements the
interface becomes a string rather than a table. MarshalTOML wins when a type
implements both, the four date-time types keep their bare
timestamp form, and text that is not valid UTF-8 is an error rather than a
replacement character.
A duration carries no text method of its own; see Durations for its rule.
Arrays
An array whose every element is a table ([]struct, []map[string]V, after
pointer dereference) emits as an array of tables. TOML also lets one array mix
tables with scalars; such an array emits as a plain value array, with the
table elements rendered as inline tables:
tree, _ := interpres.Parse([]byte(`arr = [1, {a = 2}, "x"]`))
out, _ := interpres.Marshal(tree) // arr = [1, {a = 2}, "x"]
A []any holding only tables keeps the value-array form as well, because that
is the shape Parse gives a value array of inline tables; emitting it as
[[headers]] would re-parse as []map[string]any and change the value's type
across a round-trip.
Empty arrays
A nil slice is always omitted. An empty (length 0) array of tables is always
omitted, because TOML forbids an empty [[a]]. Other empty arrays emit as
key = [] by default; OmitEmptyArrays() skips them as well, so
[]string{} is treated like a nil slice.
Long strings
By default every string is emitted as a basic "..." string with the escapes
TOML requires, a newline among them as \n. UseLiteralMultiline(threshold)
switches strings that contain a newline and are at least threshold bytes long
to the literal '''...''' form, which carries the newlines verbatim:
out, err := interpres.NewEncoder().UseLiteralMultiline(80).Marshal(cfg)
Single-line strings keep the basic form regardless of the threshold, and a
threshold of 0 or less disables the option. A string the literal form cannot
carry verbatim (an embedded run of three single quotes, a control character
other than tab or newline, or a carriage return outside a CRLF pair) also keeps
the basic form, so the output always re-parses to the same value.
Inline tables
A table element of a value array, and a sub-table inlined by
InlineTables, is written as one {a = 1, b = 2} line
while it fits. An inline table that would pass the hundredth column carries
newlines and a trailing comma instead, which TOML 1.1 allows:
arr = [1, {
n = 1,
name = "a value long enough to push this line well past the one hundred column limit",
}]
The closing brace and the entries are indented one tab per nesting level, a nested table is measured on its own line, and the output re-parses to the same value either way.
Compact documents
InlineTables(threshold) writes a sub-table as an inline table when its
single-line rendering is at most threshold bytes, and as a table header
section when it is longer. A document of small tables therefore grows shorter:
out, err := interpres.NewEncoder().InlineTables(60).Marshal(cfg)
With 60 and a table of three short entries, the same value is written
server = {host = "127.0.0.1", port = 9090, tls = {on = false}}
instead of three lines under a [server] header and a [server.tls] section.
A nested sub-table takes part in the same way, and the whole option is off at
0 or less. Two limits are deliberate. An array of tables keeps the [[a]]
header form, because its inline form re-parses as a value array and would change
the value's Go type. And because an inlined table is a value line, every one of
them precedes the first header of its document, so a table inlined next to a
header is not read back as part of that header's section.
Cancellation
MarshalContext and (*Encoder).MarshalContext accept a context.Context. The
context is checked before any work and every 64 fields during the reflection
walk.
What is not preserved
The output is not byte-identical to any document that produced the value:
- comments are dropped, and whitespace inside expressions is normalised
- map keys are emitted in sorted order
- the choice between
[table]headers and inline tables is not preserved - strings use the basic quoted form unless the literal option above applies
- a date-time drops its zero seconds and the trailing zeros of its fraction, so
07:32:00is written07:32; both are the same value - floats always carry a
.or an exponent, so a float1is emitted as1.0and stays distinguishable from the integer1across a round-trip; negative zero is normalised to0.0
The output is guaranteed to re-parse through Parse into an equivalent value
tree. Marshal cannot encode cyclic data structures.
Flow
sequenceDiagram
participant Caller
participant Marshal as Marshal
participant Walk as reflection walk
participant Emit as emitter
Caller->>Marshal: v any
Marshal->>Walk: build tomlDoc from struct or map
Walk-->>Marshal: tomlDoc or wrapped error
Marshal->>Emit: emitDoc(doc)
Emit-->>Marshal: bytes or error
Marshal-->>Caller: bytes, error
Types
type SyntaxError struct{ Line, Offset, Column int; Msg string }
Describes a document the parser rejected: the 1-based Line at which it gave
up, the Offset in bytes the scan stopped at, the 1-based Column on that
line, and Error() rendering as interpres: line N: msg. A malformed document
is the usual cause; the nesting limit and an input that is not valid UTF-8
report through the same type, with the UTF-8 message naming the offset of the
first invalid byte. Read the structured fields with a type assertion or
errors.AsType:
if se, ok := errors.AsType[*interpres.SyntaxError](err); ok {
fmt.Println(se.Line, se.Offset, se.Column, se.Msg)
fmt.Println(se.SourceLine(data)) // the line, with a caret under Offset
}
SourceLine(src) renders the source line the error points at from src,
followed by a caret line marking the column, for messages the reader sees
under the input.
type DecodeError struct{ Path []string; Err error }
Wraps a decoding failure with the key path at which it happened. Path lists
one segment per level from the document root, the outermost key first: a key
contributes its name, an array element its bracketed index, so the path of the
weight field in the first item reads ["items", "[0]", "weight"]. The
rendered message is unchanged by the type; read the fields instead of parsing
the message:
if de, ok := errors.AsType[*interpres.DecodeError](err); ok {
fmt.Println(de.Path, de.Err)
}
type EncodeError struct{ Path string; Err error }
Wraps an encoding failure with the key path of the value that failed, in the
document's own notation: server.ports[2]. Read it with errors.AsType the
same way.
type Decoder
Configurable strictness for decoding, constructed with NewDecoder. Set up
with the chainable methods, then call Decode or DecodeContext any number
of times. A configured Decoder holds no per-call state and is safe for
concurrent use. For a single document, UnmarshalWithOptions(data, v, DecodeOptions{...}) sets the same options without the Decoder; its zero
value takes the defaults.
| Method | Default | Effect |
|---|---|---|
DisallowUnknownFields() |
off | a key with no matching struct field is an error |
UseNumber() |
off | integers and floats decode into Number, which carries the literal; see Numbers as literals |
MaxDepth(depth int) |
10000 |
bound how deeply arrays and inline tables may nest |
MaxInputSize(size int) |
no limit | bound the size of the document, in bytes |
The nesting limit protects the stack, because the parser is a recursive
descent: a deeper document is rejected with a SyntaxError naming the limit
rather than running the stack out. Parse and ParseContext carry that same
default but take no options. The size limit is off by default, because the
caller already holds the bytes and the size is therefore a policy, not a
protection the library can impose on its own.
type Encoder
Configurable emission policy, constructed with NewEncoder. The option state
is private; set it with the chainable methods, each of which returns the
encoder:
| Method | Default | Effect |
|---|---|---|
GroupByKind(v bool) |
true |
group entries as scalars, then sub-tables, then arrays of tables; false preserves declaration order |
OmitEmptyArrays() |
off | skip key = [] for empty scalar arrays |
UseLiteralMultiline(threshold int) |
0 |
emit multi-line strings of at least threshold bytes as literal '''...''' |
InlineTables(threshold int) |
0 |
write a sub-table inline when its single-line form is at most threshold bytes |
out, err := interpres.NewEncoder().
GroupByKind(false).
OmitEmptyArrays().
UseLiteralMultiline(80).
InlineTables(60).
MarshalContext(ctx, cfg)
A configured Encoder holds no per-call state; each Marshal or
MarshalContext call copies the options and is safe for concurrent use, as
long as no setter races with a call.
type Document, type Table, type Entry
See Documents. A Document is what Parse returns, and it is
not a value Marshal accepts.
type Marshaler interface{ MarshalTOML() (any, error) }
See Custom encoding.
type Unmarshaler interface{ UnmarshalTOML(data any) error }
See Custom decoding. UnmarshalerContext
carries the decode's context through UnmarshalTOMLContext(ctx, data) and
wins when a type implements both.
type Number string
The literal a number was written with, what UseNumber decodes into and what
Marshal writes back as it is. See
Numbers as literals.
type OrderedMap
The string-keyed table that keeps its key order on both the encode and the decode side. See Ordered tables.
Date-time wrappers
type OffsetDateTime struct{ time.Time } // 1979-05-27T07:32:00Z
type LocalDateTime struct{ time.Time } // 1979-05-27T07:32:00
type LocalDate struct{ time.Time } // 1979-05-27
type LocalTime struct{ time.Time } // 07:32:00.999999
Each carries a String() method returning the TOML-canonical rendering: the
seconds appear only when the value carries them, and a fractional second drops
its trailing zeros, so 07:32:00 renders as 07:32 and a half second as
00.5. The types are produced by Parse and accepted by Marshal, which
writes them through String().
Errors
The entry points return:
*SyntaxErrorfor a malformed document, with the 1-based line; the nesting limit reports through it as well*DecodeErrorfor a decoding failure, with the key path inPath*EncodeErrorfor an encoding failure, with the key path inPath- a plain error for the rest: a non-pointer decode target, a cancelled context, a key that is not valid UTF-8, an input over the size limit
Decode and encode failures carry the key path or element index in the typed
wrappers above, so errors.Is and errors.AsType see through them and the
path reads from a field instead of the message text.
Notes
The exported surface is documented in godoc form in the source, and go doc .
run from the module root is the authority on signatures and types. This file
explains what the surface is for and how the parts fit together.