2026-05-02 22:19:55 +01:00
2026-05-02 22:19:55 +01:00

go-config

Go Reference License: MIT Latest Release Tests Lint Go Report Card GitHub Stars

Convert env, YAML, JSON, and INI to and from Go map[string]any and structs. Multi-source inputs merge with deep map merge: nested maps combine, scalar leaves are last-write-wins, and slices default to replace (opt-in concat via WithSliceMerge). Keys are normalized with a configurable lower+alnum rule so sub-service, SUB_SERVICE, and SubService line up across formats. Built on go-viper/mapstructure/v2.

Architecture

flowchart TB
  Sources["Sources\nbytes reader file URL process env"]
  Parser["Parser\ngodotenv yaml ini json"]
  Norm["keymap.Walk\nNormalizer"]
  MergeOp["merge.DeepMerge"]
  Map["map string any"]
  MS["structconv\nmapstructure v2"]
  Struct["Go struct"]
  Sources --> Parser --> Norm --> MergeOp --> Map
  Map -->|Unmarshal| MS --> Struct
  Struct -->|Marshal| MS --> Map
  Map -->|WriteTo Marshal| Parser

Hero example

yamlCfg := yaml.New(yaml.WithURL("https://raw.githubusercontent.com/eSlider/mail-archive/refs/heads/master/docker-compose.yml"))
envCfg := env.New(
	env.WithFile(".default.env"), // lowest priority
	env.WithFile(".env"),
	env.WithCurrentEnvironment(), // highest priority — process env wins
)

var svc MyService
_ = yamlCfg.Unmarshal(&svc)
_ = envCfg.Unmarshal(&svc) // later sources override earlier scalar leaves; maps recurse
  • Maps merge recursively (sub-trees are combined, not replaced wholesale).
  • Scalar leaves: last-write-wins when you list sources lowest → highest priority.
  • Slices: merge.Replace by default; use WithSliceMerge(merge.Concat) to append.

Runnable offline variant: see Example_hero_offline in example_hero_test.go.

Install

go get github.com/eslider/go-config
go install github.com/eslider/go-config/cmd/envc@latest

Quick start

1. Single YAML file

c := yaml.New(yaml.WithFile("config.yaml"))
var cfg AppConfig
if err := c.Unmarshal(&cfg); err != nil { /* ... */ }

2. JSON over HTTPS with a header

c := json.New(
	json.WithURL("https://api.example.com/v1/config.json"),
	json.WithHTTPHeader("Authorization", "Bearer "+token),
)
var cfg AppConfig
_ = c.Unmarshal(&cfg)

3. Cross-format conversion (YAML → JSON)

ctx := context.Background()
m, _ := yaml.New(yaml.WithFile("in.yaml")).Map(ctx)
b, _ := json.New().Marshal(m)
os.WriteFile("out.json", b, 0o644)

CLI: envc

Command Purpose
envc convert --from yaml --to env --input - --output - Convert stdin YAML to dotenv on stdout
envc get --from yaml --path service.name config.yaml Print one path (dot-separated; segments normalized like codecs)
envc merge --from yaml --to json --output out.json a.yaml b.yaml Deep-merge multiple YAML files, emit JSON

API (all codecs)

Method Description
New(opts...) Construct codec
Map(ctx) Merged map[string]any
Unmarshal(dst) / UnmarshalContext(ctx, dst) Decode into struct (or map)
Marshal(src) / WriteTo(w, src) Encode struct or map[string]any

Shared options (each subpackage): WithBytes, WithReader, WithFile, WithURL, WithHTTPHeader, WithHTTPClient, WithKeyNormalizer, WithSliceMerge, WithTrim, WithWeaklyTyped, WithTagName, WithDecodeHook.

env adds: WithCurrentEnvironment, WithPrefix.

Cross-format mapping

Go YAML ENV
Service.SubService.Name service.sub-service.name SERVICE_SUBSERVICE_NAME

INI uses dotted sections, e.g. [service.subservice] with name=....

Module Role
go-matrix-bot Matrix bots
go-onlyoffice OnlyOffice API
go-ollama Ollama client

Release flow

Versions are computed from git history, never hardcoded in source. Commit with Conventional Commits (feat:, fix:, feat!: …) and the pipeline takes care of the rest:

  1. Every push / PR runs test.yml (matrix: Go 1.22, 1.23, stable × linux/macOS/windows, -race -shuffle=on) and lint.yml (go vet + golangci-lint).
  2. On merges to main, release-please.yml parses commits since the last tag and opens a "release PR" that bumps CHANGELOG.md and .release-please-manifest.json to the next SemVer.
  3. Merging that PR creates the git tag vX.Y.Z and a GitHub Release.
  4. The tag triggers release.yml, which runs GoReleaser to cross-compile envc (linux/darwin/windows × amd64/arm64) with -X main.version={{.Version}} injected at link time and attach archives + checksums.txt to the release.

No version string lives in Go source — envc version prints the value baked in by the release build, or dev for local go install builds.

Decisions

Repo-local ASRs: docs/asr/README.md.

License

MIT © Andriy Oblivantsev

S
Description
No description provided
Readme MIT
167 KiB
Languages
Go 87.9%
Shell 12.1%