Marshal et Unmarshal
json.Marshal transforme une valeur Go en octets JSON. json.Unmarshal remplit une valeur Go à partir d'octets JSON. Les struct tags choisissent les noms des champs JSON.
Email manque dans la première sortie à cause de omitempty et d'une chaîne vide. Unmarshal a besoin d'un pointeur (&back) ; passer la valeur elle-même renvoie une InvalidUnmarshalError.
Struct tags
La syntaxe d'un tag est json:"name,option,option".
| Tag | Effet |
|---|---|
json:"user_id" | utilise user_id comme clé |
json:"email,omitempty" | omet si false, 0, "", nil, ou une slice ou map vide |
json:",omitzero" (Go 1.24) | omet si la valeur est sa valeur zéro, ou si son IsZero() renvoie true |
json:"-" | n'encode ni ne décode jamais ce champ |
json:"-," | utilise la clé littérale - |
json:"count,string" | encode un nombre ou un bool sous forme de chaîne JSON ("42") |
| pas de tag | la clé est le nom du champ Go, UserID |
Le mot de passe ne quitte jamais la struct, le solde est entre guillemets, et deleted_at disparaît alors que created_at affiche l'heure zéro 0001-01-01T00:00:00Z. C'est cette différence qui explique l'ajout de omitzero : omitempty n'a jamais fonctionné pour les structs, et c'est une surprise de longue date avec time.Time. Avec Go 1.23 et avant, utilisez un *time.Time avec omitempty pour obtenir le même effet.
MarshalIndent(v, prefix, indent) produit une sortie lisible. Utilisez-le pour les fichiers de configuration et le débogage ; les API envoient généralement du JSON compact.
Seulement les champs exportés
encoding/json utilise la réflexion et ne voit que les champs exportés. C'est le bug JSON le plus courant en Go :
type point struct {
x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}
Pas d'erreur, pas d'avertissement, juste {} à la sortie et des champs laissés à zéro à l'entrée. Mettez une majuscule aux champs et ajoutez des tags pour les clés en minuscules.
Comment le décodage fait correspondre les champs
Lors de la désérialisation dans une struct :
- Les clés sont associées au nom du tag, ou au nom du champ, sans tenir compte de la casse.
{"NAME": "x"}remplit un champ taguéjson:"name". - Les clés sans champ correspondant sont ignorées sans bruit.
- Les champs sans clé correspondante gardent leur valeur actuelle. Unmarshal ne les réinitialise pas, donc décoder dans une struct qui contient déjà des données les fusionne.
- Une incompatibilité de type (une chaîne là où la struct a un
int) renvoie une*json.UnmarshalTypeError, mais les autres champs sont quand même remplis.
Pour refuser des clés inattendues, par exemple dans une API stricte ou un fichier de configuration avec des fautes de frappe, utilisez un Decoder avec DisallowUnknownFields :
Structure inconnue : map[string]any
Quand vous ne connaissez pas la forme à l'avance, décodez dans une map[string]any ou un any. La correspondance est fixe :
| JSON | Go |
|---|---|
| objet | map[string]any |
| tableau | []any |
| chaîne | string |
| nombre | float64 |
| true / false | bool |
| null | nil |
Deux pièges sont visibles dans la sortie. Chaque nombre est un float64, donc m["stock"].(int) échouerait. Et les entiers au-delà de 2^53 perdent en précision sous forme de float64 : l'identifiant 9007199254740993 revient sous la forme ...992. UseNumber évite les deux.
Si vous connaissez une partie de la structure, décodez cette partie dans une struct et utilisez json.RawMessage pour le reste. Il garde les octets bruts d'un champ pour que vous puissiez le décoder plus tard, une fois son type connu.
Slices, maps, pointeurs et nil
| Valeur Go | JSON |
|---|---|
slice nil (var s []int) | null |
slice vide ([]int{}) | [] |
map nil | null |
pointeur nil | null |
[]byte | une chaîne base64 |
map[string]T | un objet, clés triées |
map[int]T | un objet avec les clés entières en chaînes |
La différence entre slice nil et slice vide compte pour les clients d'API qui attendent un tableau. Initialisez avec []T{} ou make([]T, 0) quand le champ doit valoir [].
Utilisez un champ pointeur (*int, *bool) quand vous devez distinguer « absent » de « zéro » en entrée. Après désérialisation, un pointeur nil signifie que la clé manquait ou valait null ; un pointeur vers 0 signifie que le client a envoyé 0.
Flux : Encoder et Decoder
json.Marshal et Unmarshal travaillent sur des slices d'octets entières. Pour un io.Reader ou un io.Writer (un body HTTP, un fichier, stdin), utilisez json.NewDecoder et json.NewEncoder. Un Decoder peut aussi lire une suite de valeurs JSON une par une :
Encoder.Encode écrit un retour à la ligne après chaque valeur. Par défaut, Marshal comme Encoder échappent <, > et & en \u003c, \u003e et \u0026, pour que le JSON puisse être intégré sans risque dans du HTML. SetEscapeHTML(false) désactive ce comportement. Dans les handlers HTTP, json.NewDecoder(r.Body).Decode(&v) et json.NewEncoder(w).Encode(v) forment la paire habituelle.
Encodage personnalisé
Un type peut contrôler son propre JSON en implémentant json.Marshaler et json.Unmarshaler. Un cas courant est un enum iota qui doit apparaître sous forme de nom, pas de nombre :
MarshalJSON a un receveur valeur pour fonctionner avec les valeurs comme avec les pointeurs ; UnmarshalJSON a besoin d'un receveur pointeur parce qu'il modifie la valeur. Les types qui n'ont besoin que d'une forme texte peuvent plutôt implémenter encoding.TextMarshaler (MarshalText), ce qui les rend aussi utilisables comme clés de map.
Erreurs courantes
- Des noms de champs en minuscule. Ils sont ignorés sans bruit.
- Passer une valeur à
Unmarshal. Il faut un pointeur. - Ignorer l'erreur. Le JSON mal formé et les incompatibilités de type ne sont signalés que par elle.
- Supposer que les nombres d'une
map[string]anysont desint. Ce sont desfloat64. - S'attendre à ce que
omitemptysupprime une struct vide ou untime.Timeà zéro. Utilisezomitzeroavec Go 1.24, ou un pointeur. - Compter sur l'ordre des champs dans les maps. Les clés de map sont triées en sortie ; les champs de struct gardent leur ordre de déclaration.
Questions fréquentes
Comment convertir une struct en JSON en Go ?
Appelez json.Marshal(v), qui renvoie un []byte et une erreur. Seuls les champs exportés (dont le nom commence par une majuscule) sont inclus. Utilisez un struct tag comme json:"name" sur un champ pour contrôler sa clé JSON. Pour une sortie indentée, utilisez json.MarshalIndent(v, "", " ").
Pourquoi des champs de ma struct manquent-ils dans la sortie JSON ?
encoding/json ne voit que les champs exportés. Un champ nommé name (en minuscule) lui est invisible, à la sérialisation comme à la désérialisation. Mettez une majuscule au champ et définissez la clé JSON avec un tag comme json:"name".
Que fait omitempty dans le JSON de Go ?
omitempty exclut un champ de la sortie quand il contient une valeur vide : false, 0, "", un pointeur ou une interface nil, ou une slice ou une map vide. Il ne considère pas une struct ni un time.Time comme vides. Go 1.24 a ajouté omitzero, qui omet toute valeur égale à la valeur zéro de son type (ou dont la méthode IsZero() renvoie true), structs et time.Time compris.
Comment parser du JSON de structure inconnue en Go ?
Désérialisez dans une map[string]any (ou un any). Les objets deviennent des map[string]any, les tableaux des []any, les chaînes des string, les booléens des bool, et tous les nombres des float64. Utilisez des assertions de type pour lire les valeurs, et Decoder.UseNumber si de grands entiers doivent rester exacts.