6.1 KiB
interpres
A dependency-free TOML 1.0 parser for Go — standard library only, no third-party modules. Passes the entire toml-test suite (185 valid and 371 invalid cases).
interpres (Latin for interpreter) reads TOML documents into Go values with a
small, encoding/json-style API. It is built for embedding in zero-dependency
Go programs that still want their configuration in TOML.
Install
go get sourcedock.dev/petrbalvin/interpres
Requires Go 1.26 or newer. The module imports only the standard library.
Usage
Decode into a struct
package main
import (
"fmt"
"sourcedock.dev/petrbalvin/interpres"
)
type Config struct {
Title string `toml:"title"`
Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
} `toml:"server"`
}
func main() {
data := []byte(`
title = "example"
[server]
host = "127.0.0.1"
port = 9090
`)
var cfg Config
if err := interpres.Unmarshal(data, &cfg); err != nil {
panic(err)
}
fmt.Printf("%+v\n", cfg)
}
Encode from a struct
out, err := interpres.Marshal(cfg)
if err != nil {
panic(err)
}
fmt.Println(string(out))
writes the same struct back to TOML:
title = "example"
[server]
host = "127.0.0.1"
port = 9090
Untyped tree
tree, err := interpres.Parse(data) // []byte → map[string]any
out, err := interpres.Marshal(tree) // map[string]any → []byte
Custom encoding
Types that need a non-default TOML shape can implement Marshaler:
type Port int
func (p Port) MarshalTOML() (any, error) {
return int64(p), nil
}
The returned value is encoded just like any other value passed to Marshal,
which means it can be any type Marshal itself understands.
Strict decoding
Reject keys that have no matching struct field — useful for catching typos in configuration files:
err := interpres.NewDecoder().
DisallowUnknownFields().
Decode(data, &cfg)
Struct mapping
Fields are matched to TOML keys by the toml:"name" tag, or by a
case-insensitive match on the field name when no tag is present. A tag of "-"
skips the field.
| TOML value | Go type (untyped tree) |
|---|---|
| string | string |
| integer | int64 |
| float | float64 |
| boolean | bool |
| offset date-time | time.Time |
| 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 map onto the destination's concrete types
(any integer/unsigned/float width, slices, nested structs, and map[string]T).
Encoding
Marshal produces valid TOML 1.0 from any struct or map[string]V value,
applying these rules:
- The top-level value must be a struct or
map[string]V. Pointers are followed; a nil top-level pointer is an error. - Struct fields are matched by
toml:"name"tag (case-insensitive fallback to field name;toml:"-"skips). Anonymous (embedded) fields without a tag are inlined. - Maps emit keys in sorted order for deterministic output.
- Output is grouped by kind at every level: scalars come first, then sub-tables, then arrays of tables. Within each group the order matches struct field declaration order (or, for maps, sorted key order). This means in TOML terms a struct that mixes scalars and sub-tables always lays out scalars at the top of the section followed by the table headers — there is no way to interleave them and still produce a document that re-parses to the same tree.
- Slices and arrays of structs or maps become
[[a]]arrays of tables. Empty or nil arrays of tables are omitted (TOML forbids an empty[[a]]); empty arrays of scalars emit askey = []. - Other slices and arrays become TOML arrays, including mixed-type
[]any. - Scalars map to TOML scalars: bool, int64, float64, string,
time.Time(offset date-time), and the local variants (LocalDateTime,LocalDate,LocalTime). - Strings are always emitted as basic
"..."strings with the escapes TOML requires (\",\\, control characters as\uXXXX). - Keys are emitted as bare keys when they match
[A-Za-z0-9_-]+, quoted otherwise. - Floats always carry a
.or an exponent (so1is always emitted as1.0), which preserves the distinction between an integer-typed value and a float-typed value across aMarshal→Parseround-trip. - Values implementing
Marshalerare encoded by callingMarshalTOMLand using its result. nilpointer fields are omitted.
Marshal cannot encode cyclic data structures. The output is not guaranteed
to be byte-identical to any TOML document that produced the input value:
comments, whitespace, key order (for maps), the choice between [table]
headers and inline tables, and the basic-vs-literal quoting style are not
preserved. The emitted document is, however, guaranteed to be re-parsable by
interpres.Parse back into an equivalent value tree.
TOML 1.0 support
interpres implements the full TOML 1.0 grammar:
- Comments, and bare / quoted / dotted keys.
- Tables
[a.b]and arrays of tables[[a]]. - Basic and literal strings, including multiline (
"""/''') with escape sequences (\n,\t,\uXXXX,\UXXXXXXXX, line-ending backslash). - Integers in decimal, hex (
0x), octal (0o), and binary (0b) with_separators; floats with exponents andinf/nan. - Booleans, offset/local date-times, dates, and times.
- Arrays (multiline, mixed types) and inline tables.
Invalid documents are rejected per the specification (malformed numbers and floats, control characters, non-UTF-8 input, out-of-range escapes, table and inline-table redefinitions, and more).
Compliance
The parser is verified against the official
toml-test suite via
cmd/interpres-decode:
go build -o interpres-decode ./cmd/interpres-decode
toml-test ./interpres-decode
License
MIT — see LICENSE. Copyright © 2026 Petr Balvín