interpres
A TOML 1.1 parser and encoder for Go, written with the standard library
alone. interpres (Latin for interpreter) gives zero-dependency
programs an encoding/json-style API for reading and writing TOML, and passes
the entire official toml-test suite:
214 valid, 467 invalid and 214 encoder cases, zero failures.
Features
- Full TOML 1.1: bare, quoted and dotted keys; tables and arrays of
tables; basic and literal strings including multiline, with the 1.1
\eand\xHHescapes; integers in the four radixes with_separators; floats with exponents,infandnan; booleans; the four date-time kinds, seconds optional as of 1.1; arrays and inline tables, multi-line as of 1.1. - Decoding and encoding:
UnmarshalandMarshalfor structs and maps, mirroringencoding/json;ParseandParseMapfor the document with its key order and the plain untyped tree. - Strict decoding:
RejectUnknownFields(true)rejects keys that match no destination field, at every struct depth. - Custom types:
MarshalerandUnmarshalerlet a type control its own TOML representation in both directions, andencoding.TextMarshalerandTextUnmarshalerare honoured by default, sonet.IP,time.Durationand user types with text methods need no configuration. - Cancellation: the parse, decode and marshal entries have
*Contextsiblings that honour acontext.Context, checked while the work runs. - Ordered documents:
Parsegives a*Documentthat keeps the key order, tells an inline table from a header one, and carries the comments;ParseMapgives the plainmap[string]anytree. - Configurable emission:
Marshaloptions for declaration-order output, omitting empty arrays, literal multiline strings, and inlining small sub-tables.
Install
As a library:
go get sourcedock.dev/petrbalvin/interpres/v2
Requires Go 1.27.1 or newer. The module imports only the standard library.
The module is public and resolves through proxy.golang.org and sum.golang.org
like any other; no GOPROXY or GOPRIVATE setup is needed to fetch it. A machine
that sets GOPRIVATE=sourcedock.dev fetches directly from the forge instead,
which skips the proxy and the checksum database.
Quick start
git clone https://sourcedock.dev/petrbalvin/interpres.git
cd interpres
just example
just example runs the tour in examples/basic: it decodes an embedded
document into a struct, prints it, and re-encodes it under both layouts.
Usage
Decode into a struct
type Config struct {
Title string `toml:"title"`
Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
} `toml:"server"`
}
var cfg Config
err := interpres.Unmarshal(data, &cfg)
Fields match by the toml:"name" tag, or by the lower-cased field name when no
tag is present; toml:"-" skips a field. Parse returns a *Document that
also carries the key order and the comments, ParseMap returns the plain
map[string]any tree, and UnmarshalContext accepts a context.
Encode from a struct
out, err := interpres.Marshal(cfg)
writes:
title = "example"
[server]
host = "127.0.0.1"
port = 9090
Tables are laid out scalars first, then sub-tables, then arrays of tables, which is the layout that re-parses to the same tree.
Strict decoding
err := interpres.Unmarshal(data, &cfg, interpres.RejectUnknownFields(true))
A key with no matching field becomes an error instead of a silent drop.
Custom types
type Port int
func (p Port) MarshalTOML() (any, error) {
return int64(p), nil
}
type IP struct{ net.IP }
func (ip *IP) UnmarshalTOML(data any) error {
s, ok := data.(string)
if !ok {
return fmt.Errorf("ip: not a string")
}
ip.IP = net.ParseIP(s)
return nil
}
The value MarshalTOML returns is encoded in place of the receiver;
UnmarshalTOML receives the parsed value verbatim.
Options
out, err := interpres.Marshal(cfg,
interpres.Layout(interpres.LayoutKindDeclaration), // preserve declaration order
interpres.OmitEmptyArrays(true), // skip empty scalar arrays
interpres.LiteralMultiline(80), // long multi-line strings as literal blocks
)
The decode and encode calls take variadic options, the shape
encoding/json/v2 uses for its own. UnmarshalRead(r, v, opts...) and
MarshalWrite(w, v, opts...) are the streaming forms.
Cancellation
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
out, err := interpres.MarshalContext(ctx, cfg)
ParseContext, UnmarshalContext and MarshalContext follow the same pattern.
The full rules for field matching, numeric conversion and emission live in docs/API.md.
Development
just build # compile bin/interpres-decode
just test # the suite, with the 80 percent coverage floor
just toml-test # the official compliance suite, needs toml-test on PATH
just fmt # gofmt in place
See docs/DEVELOPMENT.md for the full workflow, and CONTRIBUTING.md for how to contribute.
Documentation
- docs/ARCHITECTURE.md: components and data flow
- docs/API.md: the API reference, decoding and encoding rules
- docs/CLI.md: the interpres-decode adapter and validator,
also shipped as the manual page
man/interpres-decode.1
Licence
MIT. See LICENSE.
Copyright © 2026 Petr Balvín