Overview
Fill and validate Go structs from a single map of loosely-typed values.
structs gives you the tools to work with Go's structs with some magic.
Declare the shape and its tags once, then hand it a map[string]any and it resolves each key to a field, turns the value into the field's type, fills in defaults, and validates the result.
The map can come from a decoded config file, os.Environ, parsed flags, or a merge of all three. The package never reads those sources itself, so you stay in control of where values come from.
Install
go get github.com/toaweme/structsThe three steps
Every use follows the same three steps. Bind a pointer to your struct with New, Validate the inputs without touching the struct, then Set to populate it.
type ServerConfig struct {
Host string `json:"host" default:"0.0.0.0"`
Port int `json:"port" env:"PORT" default:"8080" rules:"required"`
LogLevel string `json:"log_level" default:"info" rules:"oneof:debug,info,warn,error"`
Tags []string `json:"tags" sep:","`
Database Database `json:"database"`
}
type Database struct {
DSN string `json:"dsn" env:"DATABASE_DSN" rules:"required"`
}
cfg := &ServerConfig{}
manager := structs.New(cfg)
// in a real app this is the merge of a decoded config file and os.Environ.
inputs := map[string]any{
"host": "127.0.0.1", // matched by the "host" json tag
"PORT": "9090", // matched by the env tag, coerced string -> int
"log_level": "debug", // matched by the "log_level" json tag
"tags": "edge,beta,canary", // split on sep into a []string
"database": map[string]any{ // a nested config sub-section
"dsn": "postgres://localhost/app",
},
}
if errs, err := manager.Validate(inputs); err != nil {
log.Fatal(err)
} else if len(errs) > 0 {
log.Fatalf("config is invalid: %v", errs)
}
if err := manager.Set(inputs); err != nil {
log.Fatal(err)
}
// cfg.Port == 9090, cfg.Tags == ["edge","beta","canary"], cfg.Database.DSN set.One struct, one mixed map. PORT arrived under its env tag and lands as an int, tags arrives as one comma-separated string and lands as a slice, and the nested database sub-map resolves against Database.DSN. Each value finds the right field regardless of which source shape it came in.
Note
structs does not read the environment, files, or argv. It works on the map you build. Bringing your own sources keeps the package free of any assumption about how your program is configured.
Where to go next
Start with the guides to learn how each piece behaves, then reach for the reference when you need the exact signature or tag.
default: tag without overriding real input.ValidationCheck inputs against per-field rules and report failures without mutating the struct.ReferenceThe manager, standalone functions, rules, the tag vocabulary, and coercion helpers.