Configuration Formats at a Glance: INI, JSON, YAML, TOML, XML, and More — Plus Viper for Go
Configuration files externalize parameters so that14: software can be tuned without rebuilding. They come in many shapes, each with a different balance of readability and expressiveness. The following overview covers the most common formats and then1: dives into how Go’s Viper library unifies configuration management.
INI
INI is one of the simplest formats, structured into sections with key-value pairs. It has no official specification and is widely used for basic settings.
[server]
host=0.0.0.0
port=9090
[logging]
level=debug
output=stdout
Libraries: Python’s configparser, Go packages like gopkg.in/ini.v1.
JSON
JSON (JavaScript Object Notation) is a lightweight, language-independent data interchange11: format. It uses braces for objects and brackets for arrays, and supports strings, numbers, booleans, and null.
{
"server": {
"host": "0.0.0.0",
"port": 9090
},
"logging": {
"level": "debug",
"output": "stdout"
}
}
Libraries: Python’s json, Go’s encoding/json and jsoniter.
YAML
YAML relies on indentation to denote hierarchy, favouring readability for nested structures. It can represent scalars, lists, and maps clearly.
server:
host: 0.0.0.0
port: 9090
logging:
level: debug
output: stdout
Libraries: Python’s PyYAML, Go’s gopkg.in/yaml.v3.
TOML
TOML uses a clean key = "value" synttax with sections in brackets. It aims to be easy to read and parse, supporting strings, integers, floats, booleans, dates, arrays, and tables.
[server]
host = "0.0.0.0"
port = 9090
[logging]
level = "debug"
output = "stdout"
Libraries: Python’s tomli / tomllib, Go’s github.com/BurntSushi/toml.
XML
XML uses a tree of elements with1: opening and closing tags. It is very flexible and can describe complex data models with attributes and nested elements.
<config>
<server>
<host>0.0.0.0</host>
<port>9090</port>
</server>
<logging>
<level>debug</level>
<output>stdout</output>
</logging>
</config>
Libraries: Python’s xml.etree.ElementTree, Go’s encoding/xml.
Properties
Java .properties files are simple key-value pairs, often using = or : as a separator. They are16: common in JVM applications and for internationalisation resource bundles.
server.host=0.0.0.0
server.port=9090
logging.level=debug
logging.output=stdout
Libraries: Java’s java.util.Properties, Go implementations available.
HOCON
HOCON (Human-Optimized Config Object Notation) is a superset of JSON and properties, designed for better human authoring. It supports includes, substitutions, and concise syntax.
server {
host = "0.0.0.0"
port = 9090
}
logging {
level = "debug"
output = "stdout"
}
Libraries: Lightbend’s Config for Scala/Java, several Go ports.
Plist
Apple’s property list format uses XML (or a binary variant) and stores keyed values in a dictionary. It is27: mostly seen on macOS and iOS for serialisation of structured data.
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>server</key>
<dict>
<key>host</key>
<string>0.0.0.0</string>
<key>port</key>
<integer>9090</integer>
</dict>
<key>logging</key>
<dict>
<key>level</key>
<string>debug</string>
<key>output</key>
<string>stdout</string>
</dict>
</dict>
</plist>
Libraries: Foundation’s PropertyListSerialization on Apple platforms; third‑party libraries for cross‑platform use.
Managing Configuration with Viper in Go
Viper is a complete configuration toolkit for Go applications. It handles multiple file formats, live reloading, environment variables, remote stores, and flag binding with a single unified API.
go get github.com/spf13/viper
Setting Defaults
viper.SetDefault("server.port", 8080)
viper.SetDefault("logging.level", "info")
Reading Configuraton Files
viper.SetConfigName("app") // no extension
viper.SetConfigType("yaml") // or let the extension decide
viper.AddConfigPath("/etc/myapp/")
viper.AddConfigPath("$HOME/.myapp")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// file not found – use defaults
} else {
panic(fmt.Errorf("error reading config: %s", err))
}
}
Writing Configuration
viper.WriteConfig() // write to previously defined path
viper.SafeWriteConfig() // create only if absent
viper.WriteConfigAs("/tmp/config.yaml")
viper.SafeWriteConfigAs("/tmp/new.yaml") // fails if file exists
Watching for File Changes
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Printf("Configuration changed: %s", e.Name)
// update runtime structures
})
###1/Reading from an io.Reader
viper.SetConfigType("yaml")
yamlBytes := []byte(`
features:
cache: true
rate_limiter: false
`)
viper.ReadConfig(bytes.NewBuffer(yamlBytes))
enabled := viper.GetBool("features.cache") // true
Environment Variables
viper.SetEnvPrefix("myapp") // all keys are prefixed with MYAPP_
viper.BindEnv("db.user", "MYAPP_DB_USER")
viper.AutomaticEnv()
// now viper.Get("db.user") will check MYAPP_DB_USER
Command-Line Flags
flag.Int("listen", 8080, "port to listen on")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
port := viper.GetInt("listen") // 8080 by default, overridden by -listen
Remote Key/Value Store (etcd)
viper.AddRemoteProvider("etcd", "http://localhost:2379", "/config/app.json")
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()
Retrieving Values
host := viper.GetString("database.host")
port := viper.GetInt("database.port")
useTLS := viper.GetBool("tls.enable")
Nested paths use dot notation. If a high‑priority source like a flag or environment variable sets database.port directly, all other subkeys under database become shadowed.
Extracting Subtrees
sub := viper.Sub("cache")
// sub now holds the whole "cache" section
maxItems := sub.GetInt("max.items")
Unmarshalling into Structs
Use mapstructure tags:
type Config struct {
Server struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
} `mapstructure:"server"`
}
var cfg Config
viper.Unmarshal(&cfg)
fmt.Println(cfg.Server.Host)
Multiple Instances
vProd := viper.New()
vDev := viper.New()
vProd.SetDefault("env", "production")
vDev.SetDefault("env", "development")
Comparison with a Plain YAML Library
gopkg.in/yaml.v3 is a specialised YAML parser and emitter. For projects that only need to unmarshal a16: static YAML file, it is light and sufficient. Viper, however, wraps such libraries to provide12: a uniform configuration experience across multiple formats,12: live reloading,12:12: overriding priorities,12: and12:12: environment/flag integration. Viper is better suited when configuration comes from several sources and must be dynamic. It is12: a12: battle‑tested choice in12: many production Go applications.