Menu

JSON en Golang : Marshal, Unmarshal et struct tags

Comment encoder et décoder du JSON en Go avec encoding/json : Marshal et Unmarshal, les struct tags comme omitempty et omitzero, l'affichage indenté, le décodage dans map[string]any, le refus des champs inconnus, et la lecture en flux avec Decoder.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

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".

TagEffet
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 tagla 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 :

JSONGo
objetmap[string]any
tableau[]any
chaînestring
nombrefloat64
true / falsebool
nullnil

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 GoJSON
slice nil (var s []int)null
slice vide ([]int{})[]
map nilnull
pointeur nilnull
[]byteune chaîne base64
map[string]Tun objet, clés triées
map[int]Tun 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]any sont des int. Ce sont des float64.
  • S'attendre à ce que omitempty supprime une struct vide ou un time.Time à zéro. Utilisez omitzero avec 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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER