Multi-format Config
The env library supports .env, JSON, and YAML configuration formats with automatic format detection and loading.
Format Detection
Auto-detection Rules
| Extension | Format | Constant |
|---|---|---|
.env | .env format | FormatEnv |
.json | JSON | FormatJSON |
.yaml, .yml | YAML | FormatYAML |
| Other | Auto | FormatAuto |
DetectFormat Function
format := env.DetectFormat("config.json") // FormatJSON
format = env.DetectFormat("settings.yaml") // FormatYAML
format = env.DetectFormat("app.yml") // FormatYAML
format = env.DetectFormat(".env") // FormatEnv
format = env.DetectFormat("unknown") // FormatAuto
fmt.Println(format.String()) // "json", "yaml", "dotenv", "auto"Loading Multi-format Files
Single Format
loader.LoadFiles("config.env")
loader.LoadFiles("settings.json")
loader.LoadFiles("secrets.yaml")Mixed Formats
// Auto-detect each file's format
loader.LoadFiles("config.env", "settings.json", "secrets.yaml")Override Order
Files loaded later override earlier ones:
// Order: base -> env -> json -> yaml
loader.LoadFiles(
".env", // Base configuration
"config.json", // Overrides .env
"secrets.yaml", // Overrides config.json
)JSON Format
File Structure
{
"APP_NAME": "myapp",
"APP_PORT": "8080",
"DEBUG": "true",
"DATABASE": {
"HOST": "localhost",
"PORT": "5432"
}
}Note
Nested objects are flattened to DATABASE_HOST, DATABASE_PORT.
Key Name Resolution
Nested structures in JSON/YAML are flattened for storage. The library supports multiple key name access methods:
loader.LoadFiles("config.json")
// JSON: {"database": {"host": "localhost", "port": 5432}}
// Stored as: DATABASE_HOST=localhost, DATABASE_PORT=5432
// Method 1: Flattened key name (recommended)
host := loader.GetString("DATABASE_HOST") // localhost
port := loader.GetInt("DATABASE_PORT") // 5432
// Method 2: Dot path (auto-converted)
host := loader.GetString("database.host") // localhost
port := loader.GetInt("database.port") // 5432
// Method 3: Uppercase dot path
host := loader.GetString("DATABASE.HOST") // localhostResolution Rules:
| Input Key | Converted To |
|---|---|
"DATABASE_HOST" | "DATABASE_HOST" (exact match) |
"database.host" | "DATABASE_HOST" (dot to underscore) |
"app.config.name" | "APP_CONFIG_NAME" |
"servers.0.host" | "SERVERS_0_HOST" (array index) |
Recommended Usage
- Use flattened key names in code:
GetString("DATABASE_HOST")- clear and efficient - Readable paths in config files: JSON/YAML uses natural nested structures
Flattening Rules:
| JSON Path | Storage Key |
|---|---|
database.host | DATABASE_HOST |
database.port | DATABASE_PORT |
app.server.name | APP_SERVER_NAME |
servers.0.host | SERVERS_0_HOST |
Array Access
JSON arrays are flattened to indexed keys:
{
"servers": [
{ "host": "server1.example.com", "port": 8080 },
{ "host": "server2.example.com", "port": 8081 }
]
}// Access array elements using flattened key names
host0 := loader.GetString("SERVERS_0_HOST") // server1.example.com
port0 := loader.GetInt("SERVERS_0_PORT") // 8080
host1 := loader.GetString("SERVERS_1_HOST") // server2.example.com
// Use a loop to get all hosts
var hosts []string
for i := 0; ; i++ {
h := loader.GetString(fmt.Sprintf("SERVERS_%d_HOST", i))
if h == "" {
break
}
hosts = append(hosts, h)
}
// hosts = ["server1.example.com", "server2.example.com"]JSON Parsing Configuration
cfg := env.DefaultConfig()
// Convert null values to empty strings (default: true)
cfg.JSONNullAsEmpty = true
// Convert numbers to strings (default: true)
cfg.JSONNumberAsString = true
// Convert booleans to strings (default: true)
cfg.JSONBoolAsString = true
// Maximum nesting depth (default: 10)
cfg.JSONMaxDepth = 20Type Conversion Examples
{
"PORT": 8080,
"DEBUG": true,
"TIMEOUT": 30,
"RATES": [0.1, 0.2, 0.3]
}// JSONNumberAsString = true (default)
port := loader.GetString("PORT") // "8080" (string)
port := loader.GetInt("PORT") // 8080 (integer)
// JSONBoolAsString = true (default)
debug := loader.GetString("DEBUG") // "true" (string)
debug := loader.GetBool("DEBUG") // true (boolean)YAML Format
File Structure
# Application configuration
APP_NAME: myapp
APP_PORT: "8080"
DEBUG: true
# Database configuration
DATABASE:
HOST: localhost
PORT: "5432"
USER: postgres
PASSWORD: secret
# List values
ALLOWED_HOSTS:
- localhost
- example.comKey Name Resolution
YAML nested structures use the same flattening rules as JSON:
loader.LoadFiles("config.yaml")
// Access using flattened key names
host := loader.GetString("DATABASE_HOST") // localhost
user := loader.GetString("DATABASE_USER") // postgresArray Access
YAML lists are flattened to indexed keys:
servers:
- host: server1.example.com
port: 8080
- host: server2.example.com
port: 8081// Access using flattened key names
host0 := loader.GetString("SERVERS_0_HOST") // server1.example.com
port0 := loader.GetInt("SERVERS_0_PORT") // 8080
host1 := loader.GetString("SERVERS_1_HOST") // server2.example.com
// Get the entire list
hosts := env.GetSliceFrom[string](loader, "ALLOWED_HOSTS") // ["localhost", "example.com"]YAML Parsing Configuration
cfg := env.DefaultConfig()
// Convert null/~ values to empty strings (default: true)
cfg.YAMLNullAsEmpty = true
// Convert numbers to strings (default: true)
cfg.YAMLNumberAsString = true
// Convert booleans to strings (default: true)
cfg.YAMLBoolAsString = true
// Maximum nesting depth (default: 10)
cfg.YAMLMaxDepth = 15Type Conversion Examples
PORT: 8080
DEBUG: true
TIMEOUT: 30
RATES:
- 0.1
- 0.2
- 0.3// YAMLNumberAsString = true (default)
port := loader.GetString("PORT") // "8080" (string)
port := loader.GetInt("PORT") // 8080 (integer)
// YAMLBoolAsString = true (default)
debug := loader.GetString("DEBUG") // "true" (string)
debug := loader.GetBool("DEBUG") // true (boolean)
// List access
rates := env.GetSliceFrom[float64](loader, "RATES") // [0.1, 0.2, 0.3].env Format
File Structure
# Comments
APP_NAME=myapp
APP_PORT=8080
DEBUG=true
# Quotes
MESSAGE="Hello World"
LITERAL='literal ${noexpand}'
# Variable expansion
BASE_URL=https://api.example.com
API_URL=${BASE_URL}/v1
# Log level
LOG_LEVEL=infoVariable Expansion
cfg := env.DefaultConfig()
cfg.ExpandVariables = true // Enabled by default
loader, _ := env.New(cfg)
loader.LoadFiles(".env")
// .env content:
// BASE_URL=https://api.example.com
// API_URL=${BASE_URL}/v1
apiURL := loader.GetString("API_URL")
// Output: https://api.example.com/v1Expansion Syntax
| Syntax | Description |
|---|---|
${VAR} | Reference variable |
${VAR:-default} | Use default value when variable does not exist |
# Expansion examples
HOST=localhost
PORT=8080
# Reference other variables
URL=http://${HOST}:${PORT}
# Default values (reference another variable, use default when undefined)
TIMEOUT_VALUE=${TIMEOUT:-30s}
DEBUG_VALUE=${DEBUG:-false}export Syntax
# export prefix supported (when AllowExportPrefix = true)
export DATABASE_HOST=localhost
export DATABASE_PORT=5432YAML-style Syntax
cfg := env.DefaultConfig()
cfg.AllowYamlSyntax = true // Enable YAML-style syntax# Supports YAML-style key-value pairs
KEY: value
ANOTHER_KEY: "quoted value"Mixed Configuration Patterns
Development/Production Separation
config/
├── base.json # Base configuration
├── development.env # Development overrides
├── production.yaml # Production overrides
└── local.env # Local overrides (not committed)func loadConfig(loader *env.Loader) error {
// 1. Base configuration
if err := loader.LoadFiles("config/base.json"); err != nil {
return err
}
// 2. Environment configuration
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
switch env {
case "production":
if err := loader.LoadFiles("config/production.yaml"); err != nil {
return err
}
default:
if err := loader.LoadFiles("config/development.env"); err != nil {
return err
}
}
// 3. Local overrides (optional)
if _, err := os.Stat("config/local.env"); err == nil {
if err := loader.LoadFiles("config/local.env"); err != nil {
return err
}
}
return nil
}Separation by Function
config/
├── app.json # Application configuration
├── database.yaml # Database configuration
├── redis.env # Redis configuration
└── secrets.json # Secret configurationloader.LoadFiles(
"config/app.json",
"config/database.yaml",
"config/redis.env",
"config/secrets.json",
)Configuration Priority
CLI flags > Environment variables > Local config > Environment config > Base configSerialization
Marshal
Serialize configuration to a specified format:
data := map[string]string{
"HOST": "localhost",
"PORT": "8080",
}envStr, _ := env.Marshal(data)
// HOST=localhost
// PORT=8080jsonStr, _ := env.Marshal(data, env.FormatJSON)
// {
// "HOST": "localhost",
// "PORT": 8080
// }yamlStr, _ := env.Marshal(data, env.FormatYAML)
// HOST: localhost
// PORT: 8080Marshal Struct
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
cfg := Config{Host: "localhost", Port: 8080}envStr, _ := env.Marshal(cfg, env.FormatEnv)jsonStr, _ := env.Marshal(cfg, env.FormatJSON)yamlStr, _ := env.Marshal(cfg, env.FormatYAML)UnmarshalMap
Deserialize to a map:
envData := "HOST=localhost\nPORT=8080"
data, _ := env.UnmarshalMap(envData, env.FormatEnv)jsonData := `{"HOST":"localhost","PORT":"8080"}`
data, _ := env.UnmarshalMap(jsonData, env.FormatJSON)yamlData := "HOST: localhost\nPORT: \"8080\""
data, _ := env.UnmarshalMap(yamlData, env.FormatYAML)Auto-detect format
Pass env.FormatAuto to let the library determine the format from content: data, _ := env.UnmarshalMap(jsonData, env.FormatAuto).
UnmarshalStruct
Deserialize to a struct:
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
var cfg Configenv.UnmarshalStruct("HOST=localhost\nPORT=8080", &cfg, env.FormatEnv)env.UnmarshalStruct(`{"HOST":"localhost","PORT":"8080"}`, &cfg, env.FormatJSON)env.UnmarshalStruct("HOST: localhost\nPORT: \"8080\"", &cfg, env.FormatYAML)Custom Formats
Register a Parser
// Define format constant
const FormatTOML env.FileFormat = 100
// Implement the EnvParser interface
type TOMLParser struct {
cfg env.Config
validator env.Validator
auditor env.FullAuditLogger
}
func (p *TOMLParser) Parse(r io.Reader, filename string) (map[string]string, error) {
// Implement TOML parsing
result := make(map[string]string)
// ...
return result, nil
}
// Register the parser
func init() {
env.RegisterParser(FormatTOML, func(cfg env.Config, f *env.ComponentFactory) (env.EnvParser, error) {
return &TOMLParser{
cfg: cfg,
validator: f.Validator(),
auditor: f.Auditor(),
}, nil
})
}See Custom Parser for details.
Complete Example
package main
import (
"fmt"
"log"
"github.com/cybergodev/env"
)
func main() {
// Create loader
cfg := env.DefaultConfig()
cfg.ExpandVariables = true
loader, err := env.New(cfg)
if err != nil {
log.Fatal(err)
}
defer loader.Close()
// Load mixed-format configuration
err = loader.LoadFiles(
"config/base.json", // JSON base configuration
"config/database.yaml", // YAML database configuration
"config/app.env", // .env application configuration
)
if err != nil {
log.Fatal(err)
}
// Read configuration
fmt.Printf("App: %s\n", loader.GetString("APP_NAME"))
fmt.Printf("DB Host: %s\n", loader.GetString("DATABASE_HOST"))
fmt.Printf("DB Port: %d\n", loader.GetInt("DATABASE_PORT"))
// Export current configuration
all := loader.All()
exported, _ := env.Marshal(all, env.FormatEnv)
fmt.Println("\nExported config:")
fmt.Println(exported)
}Related Documentation
- Serialization - Serialization/deserialization details
- ComponentFactory API - Format detection and parser registration
- Custom Parser - Adding custom formats
- Config API - JSON/YAML parsing configuration