defaults

Initialize Go structs with default values using simple struct tags and reflection.

Library
Go
vv1.8.0
863stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
41/100Fair
Development Activity12
Maintenance20
Community44
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
69/100Good
Architecture80
Code Quality85
Innovation55
Learning Curve55

defaults is a small reflection-based Go library that populates struct fields with default values declared via a default struct tag. It supports nearly every Go type — scalars, slices, maps, nested and pointer structs, and type aliases like time.Duration — while preserving any non-zero values already set on the struct so defaults never clobber real data.

For values that can’t be expressed as a static tag, structs can implement the Setter interface to compute defaults dynamically at initialization time, and the library exposes a CanUpdate helper so those setters can safely check whether a field is still at its zero value before overwriting it.

What You Get

  • Tag-driven defaults - Declare a default value per field with a default:"..." struct tag instead of writing manual initialization code.
  • Deep recursive initialization - Nested structs, slices of structs, maps of structs, and pointer fields are all walked and initialized automatically.
  • Non-destructive by design - Only zero-value fields are touched; any value already set on the struct before calling Set is preserved.
  • Dynamic defaults via Setter - Structs can implement SetDefaults() to compute a default at runtime (e.g. a random value) instead of a static tag.
  • Broad type coverage - All numeric kinds, bool, string, time.Duration, aliased types, and anything implementing encoding.TextUnmarshaler/json.Unmarshaler are supported out of the box.

Common Use Cases

  • Config struct initialization - Populate a configuration struct’s unset fields with sane defaults after unmarshaling partial YAML/JSON/env input.
  • Test fixture construction - Build fully-populated struct instances for tests without repeating boilerplate default assignments in every test case.
  • API request/response DTOs - Ensure optional fields on request structs have sensible defaults before validation or downstream processing.
  • Library option structs - Give a package’s Options/Config struct baked-in defaults so callers only need to set the fields they care about.

Under The Hood

Architecture The core lives in a single file (defaults.go) exposing Set/MustSet, which operates on a struct pointer via reflect. Field-level work is dispatched in setField, which first assigns scalar/container zero values based on the tag and reflect.Kind, then a trailing switch recurses into Ptr, Struct, Slice, and Map fields to handle nesting. The Setter interface (setter.go) is a small, isolated extension point invoked via callSetter after each Set/setField pass, letting types opt into runtime-computed defaults without touching the core recursion logic. There’s no dependency injection or external state — everything operates in place on the struct passed in, which keeps the design a tight, single-purpose monolith appropriate for its scope; the one blast-radius risk is that all consumers share the same Set entry point, so a signature change ripples to every caller.

Tech Stack The module (github.com/creasty/defaults, Go 1.14) depends only on the standard library — reflect, encoding, encoding/json, errors, strconv, time — with zero third-party runtime dependencies, and the test suite likewise sticks to stdlib testing and reflect.DeepEqual rather than pulling in an assertion library. CI runs on CircleCI: go mod download, golangci-lint, make ci-test, and a Codecov coverage upload. Being a library rather than a service, there’s no build/deployment target beyond go build.

Code Quality The test file is extensive (700+ lines) and exercises nearly every supported type — scalar variants, octal literals, aliased types, encoding.TextUnmarshaler via net.IP, JSON-unmarshaled structs, Setter interaction, pointers, and nested maps/slices — using straightforward stdlib assertions. Error handling is explicit and typed (a sentinel errInvalidType returned from Set, with a separate MustSet wrapper for callers who prefer a panic), and naming follows idiomatic Go conventions throughout. CI enforces golangci-lint and coverage reporting as automated quality gates beyond the tests themselves.

What Makes It Unique The library’s distinguishing choice is pairing struct-tag defaults with a strict “zero value means unset” contract, enforced via reflect.DeepEqual against the type’s zero value, so Set never overwrites a field a caller has already populated — a guarantee simpler default-value helpers skip. It extends that same contract to dynamic defaults through the Setter interface and a CanUpdate helper, and special-cases encoding.TextUnmarshaler/json.Unmarshaler so tags can carry structured values without bespoke per-type parsing. These are meaningful refinements on a well-established Go pattern (tag-driven struct defaults via reflection) rather than a fundamentally new technique.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search