config

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT Imports: 0 Imported by: 0

README

go-config

Go Reference Latest Release Tests Lint Go Report Card GitHub Stars

Convert env, YAML, JSON, TOML, 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 json toml ini"]
  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

Install the binary from Install (go install …/cmd/envc@latest). Every subcommand uses --from and --to with one of yaml, json, toml, ini, env. Snippets use bash so you can copy-paste; replace paths and URLs with yours.

Help and version
# Root usage (commands, short descriptions)
envc help

# Per-command flag reference (convert, merge, get)
envc convert -h
envc merge -h
envc get -h

# Version string, git commit, build date (release builds embed the tag; local go install → dev)
envc version
convert

One input → normalize keys → one output. Defaults: --input -, --output - (stdin / stdout).

# Helm-style values file → JSON on the terminal (redirect to a file if you prefer)
envc convert --from yaml --to json --input ./values.yaml --output -

# Teammate’s JSON app settings → YAML for a repo that only accepts YAML
envc convert --from json --to yaml --input ./settings.json --output ./settings.yaml

# Windows-style INI → JSON for a one-off jq filter
envc convert --from ini --to json --input ./odbc.ini --output ./odbc.json

# TOML (e.g. app / tool config) → YAML for a stack that only reads YAML
envc convert --from toml --to yaml --input ./config.toml --output ./config.yaml

# Remote YAML → materialize .env for `docker compose --env-file` or similar
envc convert --from yaml --to env \
  --input "https://raw.githubusercontent.com/org/stack/main/config.yaml" \
  --output ./.env.generated

# Tiny inline document → JSON (stdin is the pipe; same idea as --input -)
printf 'service:\n  name: api\n  port: 8443\n' | envc convert --from yaml --to json
merge

Several inputs in order: nested maps combine, scalar leaves last-write-wins, slices default to replace. Optional --output - (stdout).

# Docker Compose: base + override → single JSON for another tool in the pipeline
envc merge --from yaml --to json \
  ./docker-compose.base.yaml \
  ./docker-compose.override.yaml

# App config: shipped defaults, local overrides, generated secrets → one merged YAML artifact
envc merge --from yaml --to yaml \
  --output ./config.merged.yaml \
  ./config.defaults.yaml \
  ./config.local.yaml \
  ./config.secrets.yaml

# Hotfix on stdin, then merge with on-disk YAML (--from must match every input, including stdin)
cat ./patch-canary.yaml | envc merge --from yaml --to json - ./config.base.yaml ./config.prod.yaml
get

Print one scalar or JSON-encoded value. --path is dot-separated; each segment uses the same lower+alnum rules as the library (sub-service / SubServicesubservice). Paths follow maps only (YAML lists are not walked by index here).

# Image line from docker-compose (good for scripts: stdout is just the value)
envc get --from yaml --path services.api.image ./docker-compose.yaml

# Nested string from an application config on disk
envc get --from yaml --path database.url ./config/app.yaml

# Same lookup, but YAML arrives from curl (positional "-" = read stdin to EOF)
curl -fsSL https://config.example.com/app.yaml | envc get --from yaml --path database.url -
Load YAML or INI into the current shell

--to env emits KEY=value (shell assignments, not export). Use set -a (allexport) so child processes inherit variables while you source. source <(…) requires bash. Only use with trusted input (same risk as any source).

# Stack defaults from YAML into the current shell session
set -a
source <(envc convert --from yaml --to env --input ./.env.defaults.yaml)
set +a

# Legacy INI (e.g. PHP) → env-style assignments in the shell
set -a
source <(envc convert --from ini --to env --input ./legacy.ini)
set +a

# TOML tool manifest or stack file → env in the shell
set -a
source <(envc convert --from toml --to env --input ./stack.toml)
set +a

# Inline YAML here-doc → env → source (CI or local; no intermediate file)
set -a
source <(cat <<'YAML' | envc convert --from yaml --to env
app:
  env: staging
  region: eu-west-1
YAML
)
set +a
Stdin, URLs, and EOF
# Explicit stdin redirect (reads until EOF)
envc convert --from yaml --to json --input - --output - <./service.yaml

# Default is stdin/stdout — safe when stdin is a pipe or file; on an interactive TTY with no
# pipe, the process waits for Ctrl-D, which looks like a "hang". Prefer --input path/URL in scripts.
printf 'k: v\n' | envc convert --from yaml --to json

# HTTPS GET with client timeout; full body is read into memory before convert
envc convert --from json --to yaml \
  --input "https://api.example.com/v1/config.json" \
  --output ./snapshot.yaml

API (format 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=....

TOML uses explicit tables, e.g. [service], [service.subservice], with name = "...".

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

Contributing

Testing expectations, local commands, commit message conventions, how release-please and GoReleaser publish tags and envc binaries, and architecture decisions (repo ASRs) are documented in CONTRIBUTING.md.

License

MIT © Andriy Oblivantsev

Documentation

Overview

Package config is the module root for go-config (import path github.com/eslider/go-config). Use the subpackages env, yaml, json, toml, and ini for format-specific codecs and the cmd/envc command for CLI conversion.

Hero workflow (YAML + layered dotenv + process env):

yamlCfg := yaml.New(yaml.WithURL("https://example.com/config.yaml"))
envCfg := env.New(
    env.WithFile(".default.env"),
    env.WithFile(".env"),
    env.WithCurrentEnvironment(),
)
var svc MyService
_ = yamlCfg.Unmarshal(&svc)
_ = envCfg.Unmarshal(&svc)
Example (Hero_offline)
package main

import (
	"fmt"

	"github.com/eslider/go-config/env"
	"github.com/eslider/go-config/yaml"
)

func main() {
	// README hero pattern using offline sources (no network, no .env files).
	y := yaml.New(yaml.WithBytes([]byte(`service: { name: demo }`)))
	e := env.New(env.WithBytes([]byte("SERVICE_PORT=8080")))
	var m map[string]any
	_ = y.Unmarshal(&m)
	var flat map[string]any
	_ = e.Unmarshal(&flat)
	fmt.Println(m["service"].(map[string]any)["name"], flat["service"].(map[string]any)["port"])
}
Output:
demo 8080

Directories

Path Synopsis
cmd
envc command
Command envc converts, queries, and merges configuration between env, YAML, JSON, TOML, and INI formats.
Command envc converts, queries, and merges configuration between env, YAML, JSON, TOML, and INI formats.
Package env loads dotenv files and process environment variables into nested maps and Go structs.
Package env loads dotenv files and process environment variables into nested maps and Go structs.
Package ini loads INI configuration from bytes, files, or URLs into maps and structs.
Package ini loads INI configuration from bytes, files, or URLs into maps and structs.
internal
bytesutil
Package bytesutil reads entire sources into memory.
Package bytesutil reads entire sources into memory.
keymap
Package keymap normalizes map keys recursively for cross-format matching.
Package keymap normalizes map keys recursively for cross-format matching.
merge
Package merge provides deep map merging with configurable slice behaviour.
Package merge provides deep map merging with configurable slice behaviour.
source
Package source provides pluggable configuration inputs (bytes, files, URLs).
Package source provides pluggable configuration inputs (bytes, files, URLs).
structconv
Package structconv wraps mapstructure for map<->struct conversion.
Package structconv wraps mapstructure for map<->struct conversion.
testfixtures
Package testfixtures resolves paths to files under the module's fixtures/ directory.
Package testfixtures resolves paths to files under the module's fixtures/ directory.
Package json loads JSON configuration from bytes, files, or URLs into maps and structs, with optional key normalization and multi-source merging.
Package json loads JSON configuration from bytes, files, or URLs into maps and structs, with optional key normalization and multi-source merging.
Package toml loads and writes TOML configuration using the shared codec pattern (sources, deep merge, key normalization, struct decode via mapstructure).
Package toml loads and writes TOML configuration using the shared codec pattern (sources, deep merge, key normalization, struct decode via mapstructure).
Package yaml loads YAML configuration from bytes, files, or URLs into maps and structs, with optional key normalization and multi-source merging.
Package yaml loads YAML configuration from bytes, files, or URLs into maps and structs, with optional key normalization and multi-source merging.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL