La magie du JSON en Go : pourquoi certains champs sont visibles et d'autres non ?
\nAvez-vous déjà été confronté à une situation où votre structure Go envoie dans la réponse JSON ce que le client n'a pas besoin de voir ? Par exemple, le mot de passe d'un utilisateur, l'ID interne de la base de données ou une clé secrète. Ou, à l'inverse, devez-vous afficher un champ uniquement sous certaines conditions (par exemple, s'il n'est pas vide).
\nCe problème est l'un des plus populaires sur Stack Overflow, et pour cause. Travailler avec JSON en Go est un art d'équilibre entre la rigueur des types et la flexibilité de la représentation. Aujourd'hui, nous allons passer en revue toutes les méthodes : du simple tag json:"-" aux solutions personnalisées complexes avec des interfaces. C'est parti !
1. La méthode la plus simple : le tag json:"-"
\nGo permet de contrôler la sérialisation d'une structure via des tags spéciaux. Si vous voulez masquer définitivement un champ du JSON, ajoutez simplement json:"-".
type User struct {\n Name string `json:"name"`\n Password string `json:"-"`\n Age int `json:"age"`\n}\n\nfunc main() {\n u := User{Name: "Alice", Password: "supersecret", Age: 30}\n data, _ := json.Marshal(u)\n fmt.Println(string(data))\n // Sortie : {"name":"Alice","age":30}\n}\nLe champ Password a disparu, comme s'il n'avait jamais existé. Idéal pour les données confidentielles qui ne doivent jamais quitter le serveur.
2. Masquage conditionnel : omitempty
\nParfois, vous devez masquer un champ uniquement s'il est vide. Pour cela, on utilise l'option omitempty (abréviation de "omit if empty").
type Product struct {\n Name string `json:"name"`\n Description string `json:"description,omitempty"`\n Price float64 `json:"price,omitempty"`\n}\n\nfunc main() {\n p1 := Product{Name: "Livre", Price: 500.0}\n data1, _ := json.Marshal(p1)\n fmt.Println(string(data1)) // {"name":"Livre","price":500}\n\n p2 := Product{Name: "Cours"}\n data2, _ := json.Marshal(p2)\n fmt.Println(string(data2)) // {"name":"Cours"}\n}\nImportant : omitempty fonctionne uniquement avec les valeurs "nulles" : "", 0, false, nil (pour les slices, maps, pointeurs).
Piège avec les champs numériques
\nSoyez prudent : si Price est égal à 0, il sera également masqué. Dans certaines logiques métier, cela est inacceptable (un produit gratuit est aussi un prix). Dans ce cas, il est préférable d'utiliser des pointeurs.
3. Utilisation de pointeurs (*) et d'interfaces
\nLes pointeurs sont vos meilleurs amis lorsque vous devez distinguer "valeur non spécifiée" et "valeur égale à zéro".
\ntype UserProfile struct {\n Name string `json:"name"`\n Age *int `json:"age,omitempty"` // pointeur vers int\n Bio *string `json:"bio,omitempty"`\n}\n\nfunc main() {\n // Âge non spécifié — le champ sera masqué\n u1 := UserProfile{Name: "Jean"}\n data1, _ := json.Marshal(u1)\n fmt.Println(string(data1)) // {"name":"Jean"}\n\n // Âge = 0 — c'est une valeur significative\n zero := 0\n u2 := UserProfile{Name: "Marie", Age: &zero}\n data2, _ := json.Marshal(u2)\n fmt.Println(string(data2)) // {"name":"Marie","age":0}\n}\nCette approche est souvent utilisée dans les API REST, où vous devez mettre à jour uniquement les champs transmis (mise à jour partielle, requêtes PATCH).
\n\n4. Sérialisation personnalisée : l'interface json.Marshaler
\nSi les tags standards ne suffisent pas, vous pouvez contrôler entièrement le processus. Implémentez l'interface json.Marshaler et écrivez votre propre logique.
type DynamicUser struct {\n ID int\n Name string\n ShowEmail bool\n Email string\n}\n\nfunc (u DynamicUser) MarshalJSON() ([]byte, error) {\n // Créer une structure anonyme uniquement pour la sérialisation\n m := map[string]interface{}{\n "id": u.ID,\n "name": u.Name,\n }\n if u.ShowEmail {