
Coder les réglages en dur fige un outil ; charger une configuration typée depuis un fichier le rend adaptable sans le recompiler. Ce guide vous apprend à décrire votre configuration dans une structure Go annotée de struct tags, à la remplir depuis un fichier YAML (gopkg.in/yaml.v3) ou TOML (github.com/BurntSushi/toml), à poser des valeurs par défaut pour les champs absents, à valider le résultat, et à laisser des drapeaux de ligne de commande écraser certaines valeurs. Public visé : vous connaissez les structures et les modules. Exemples testés sur Go 1.26, avec yaml.v3 v3.0.1 et BurntSushi/toml v1.6.0.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Décrire une configuration dans une structure avec des struct tags.
- Charger un fichier YAML et un fichier TOML dans cette même structure.
- Poser des valeurs par défaut et valider les champs manquants.
- Écraser la configuration avec des drapeaux de ligne de commande.
Une structure typée pour la configuration
Section intitulée « Une structure typée pour la configuration »Le point de départ est une structure Go qui reflète l'arborescence de votre fichier. Chaque champ porte un struct tag qui indique la clé correspondante dans le fichier. Un même champ peut déclarer plusieurs tags, ici yaml et toml, pour être lisible depuis les deux formats :
type Serveur struct { Hote string `yaml:"hote" toml:"hote"` Port int `yaml:"port" toml:"port"`}
type Config struct { Serveur Serveur `yaml:"serveur" toml:"serveur"` LogLevel string `yaml:"log_level" toml:"log_level"`}Deux règles conditionnent le bon fonctionnement du décodage. D'abord, les champs doivent être exportés (initiale majuscule) : les bibliothèques utilisent la réflexion, qui ne voit pas les champs privés. Ensuite, le tag mappe la clé exacte du fichier au champ, ce qui vous laisse nommer vos champs à la mode Go (LogLevel) tout en lisant une clé en snake_case (log_level).
Charger un fichier YAML
Section intitulée « Charger un fichier YAML »La bibliothèque de référence est gopkg.in/yaml.v3. On lit le fichier avec os.ReadFile, puis yaml.Unmarshal remplit la structure passée par pointeur :
import "gopkg.in/yaml.v3"
func chargerYAML(chemin string) (Config, error) { var cfg Config data, err := os.ReadFile(chemin) if err != nil { return cfg, err } if err := yaml.Unmarshal(data, &cfg); err != nil { return cfg, err } return cfg, nil}Avec ce fichier config.yaml :
serveur: hote: 0.0.0.0 port: 9090log_level: debugLa structure obtenue reflète fidèlement le contenu :
YAML complet : {Serveur:{Hote:0.0.0.0 Port:9090} LogLevel:debug}Charger un fichier TOML
Section intitulée « Charger un fichier TOML »Le format TOML se lit avec github.com/BurntSushi/toml. Sa fonction toml.DecodeFile enchaîne la lecture et le décodage en un seul appel, et renvoie en prime des métadonnées utiles :
import "github.com/BurntSushi/toml"
func chargerTOML(chemin string) (Config, error) { var cfg Config if _, err := toml.DecodeFile(chemin, &cfg); err != nil { return cfg, err } return cfg, nil}Avec ce config.toml :
log_level = "warn"
[serveur]hote = "10.0.0.1"port = 7070Le résultat remplit la même structure Config que le YAML :
TOML : {Serveur:{Hote:10.0.0.1 Port:7070} LogLevel:warn}Valeurs par défaut pour les champs absents
Section intitulée « Valeurs par défaut pour les champs absents »Un fichier de configuration réel est souvent partiel : l'utilisateur ne renseigne que ce qu'il veut changer. L'astuce est de pré-remplir la structure avec vos valeurs par défaut avant l'Unmarshal. Les décodeurs ne touchent qu'aux champs présents dans le fichier, laissant les autres intacts :
func valeursParDefaut() Config { return Config{ Serveur: Serveur{Hote: "127.0.0.1", Port: 8080}, LogLevel: "info", }}
func chargerYAML(chemin string) (Config, error) { cfg := valeursParDefaut() // <- défauts d'abord data, err := os.ReadFile(chemin) if err != nil { return cfg, err } return cfg, yaml.Unmarshal(data, &cfg)}Avec un fichier qui ne précise que l'hôte :
serveur: hote: 192.168.1.10Le port et le niveau de log conservent leurs valeurs par défaut, seule l'hôte est remplacée :
YAML partiel : {Serveur:{Hote:192.168.1.10 Port:8080} LogLevel:info}Valider après le chargement
Section intitulée « Valider après le chargement »Un décodage réussi ne garantit pas une configuration cohérente : un port peut être hors bornes, un champ requis peut manquer. Ajoutez une méthode de validation appelée juste après le chargement, qui renvoie une erreur explicite :
func (c Config) valider() error { if c.Serveur.Port < 1 || c.Serveur.Port > 65535 { return fmt.Errorf("port invalide: %d", c.Serveur.Port) } return nil}message: port invalide: 70000Cette étape est d'autant plus importante qu'une clé absente laisse le champ à sa valeur nulle ("", 0, nil) sans provoquer d'erreur. La validation est votre filet de sécurité contre les configurations incomplètes.
Écraser la configuration avec des drapeaux
Section intitulée « Écraser la configuration avec des drapeaux »En pratique, on veut souvent qu'un drapeau de ligne de commande prenne le pas sur le fichier, par exemple pour changer un port le temps d'un test. L'astuce est d'utiliser la valeur issue de la configuration comme valeur par défaut du drapeau : le drapeau ne l'emporte que s'il est réellement fourni.
port := flag.Int("port", cfg.Serveur.Port, "port du serveur")flag.Parse()cfg.Serveur.Port = *portSans drapeau, la valeur du fichier est conservée ; avec -port 3000, elle est remplacée :
sans flag : port effectif: 8080avec -port 3000: port effectif: 3000Le drapeau ne l'emporte que s'il est fourni, sinon la valeur du fichier reste en place.
Compléter avec l'environnement, gérer l'absence de fichier
Section intitulée « Compléter avec l'environnement, gérer l'absence de fichier »Deux besoins achèvent ce socle en production. D'abord, un déploiement conteneurisé préfère souvent surcharger un réglage par une variable d'environnement plutôt que d'éditer un fichier. Lisez-la avec os.Getenv après le chargement, et n'écrasez la valeur que si elle est définie :
func appliquerEnv(cfg *Config) { if v := os.Getenv("APP_PORT"); v != "" { if p, err := strconv.Atoi(v); err == nil { cfg.Serveur.Port = p } }}Ensuite, l'absence de fichier ne doit pas être fatale : un outil doit pouvoir démarrer sur ses seuls défauts. Distinguez le cas « fichier absent » des vraies erreurs de lecture avec errors.Is(err, os.ErrNotExist) :
data, err := os.ReadFile(chemin)if errors.Is(err, os.ErrNotExist) { return valeursParDefaut(), nil // pas de fichier: on garde les défauts}fichier absent -> defaut: 8080après APP_PORT=5000 : 5000Vous obtenez alors la hiérarchie complète que suivent la plupart des outils : défauts, puis fichier, puis variables d'environnement, puis drapeaux, chaque source affinant la précédente.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »Le chargement de configuration réserve deux pièges silencieux, qui reviennent constamment dans les questions des débutants Go. Aucun ne provoque d'erreur par défaut : votre programme démarre, mais avec une valeur inattendue.
| Symptôme | Cause | Correction |
|---|---|---|
| Un champ reste toujours à zéro | champ non exporté (initiale minuscule) : la réflexion l'ignore | Mettre une majuscule au nom du champ |
| Une clé du fichier n'a aucun effet | faute de frappe dans la clé, silencieusement ignorée | Décodage strict (voir ci-dessous) ; valider les champs requis |
Une valeur écrase un défaut avec 0/"" | la clé est présente mais vide dans le fichier | Retirer la clé du fichier, ou valider les bornes |
| Panique au chargement | fichier absent ou illisible, erreur non gérée | Traiter l'erreur de os.ReadFile / DecodeFile |
Le piège le plus courant est la faute de frappe dans une clé. Écrire prot: au lieu de port: ne déclenche rien : le champ reste à 0 et le programme tourne avec une valeur fantôme. Chaque bibliothèque offre un mode strict qui transforme ce silence en erreur. Côté YAML, un décodeur avec KnownFields(true) refuse toute clé inconnue :
dec := yaml.NewDecoder(bytes.NewReader(data))dec.KnownFields(true)err := dec.Decode(&cfg)// yaml: unmarshal errors: line 1: field prot not found in type main.ConfigCôté TOML, toml.Decode renvoie des métadonnées dont Undecoded() liste les clés du fichier qui n'ont trouvé aucun champ :
md, _ := toml.Decode(data, &cfg)fmt.Println(md.Undecoded()) // [prot]En développement, activez ce mode strict pour repérer les fautes de frappe immédiatement. En production, restez tolérant si vous voulez accepter des clés futures, mais validez toujours les réglages critiques après le chargement.
Exercice : une config avec défaut et validation
Section intitulée « Exercice : une config avec défaut et validation »Assemblez les briques. Cherchez la solution avant d'ouvrir la réponse.
Complétez la structure Config avec un champ MaxConnexions int (clé max_connexions), une valeur par défaut de 100, et une validation qui refuse une valeur négative.
Chargez ce YAML partiel, qui ne fixe que le niveau de log :
log_level: debugLe résultat attendu doit garder MaxConnexions à 100 et LogLevel à debug.
Le défaut posé avant Unmarshal survit à un fichier qui ne mentionne pas la clé.
type Config struct { LogLevel string `yaml:"log_level"` MaxConnexions int `yaml:"max_connexions"`}
func valeursParDefaut() Config { return Config{LogLevel: "info", MaxConnexions: 100}}
func (c Config) valider() error { if c.MaxConnexions < 0 { return fmt.Errorf("max_connexions négatif: %d", c.MaxConnexions) } return nil}
func main() { cfg := valeursParDefaut() yaml.Unmarshal([]byte("log_level: debug\n"), &cfg) fmt.Printf("%+v\n", cfg) fmt.Println(cfg.valider())}{LogLevel:debug MaxConnexions:100}<nil>MaxConnexions reste à 100 car la clé n'apparaît pas dans le fichier, tandis que LogLevel passe à debug. La validation renvoie nil : la configuration est cohérente.
À retenir
Section intitulée « À retenir »- La configuration se décrit dans une structure Go dont les champs, exportés, portent des struct tags (
yaml:"...",toml:"..."). yaml.Unmarshal(gopkg.in/yaml.v3) ettoml.DecodeFile(BurntSushi/toml) remplissent la même structure passée par pointeur.- Poser les valeurs par défaut avant l'
Unmarshalpréserve les champs que le fichier ne mentionne pas. - Une clé absente ou mal orthographiée laisse le champ à zéro sans erreur : validez après chargement.
- Le mode strict (
KnownFields(true)en YAML,Undecoded()en TOML) transforme les fautes de frappe en erreurs. - Utiliser la valeur du fichier comme défaut d'un drapeau donne la hiérarchie défauts, fichier, drapeaux.