JSON en Go
Génère des structs Go avec balises json depuis JSON : nombres typés selon leur écriture, pointeurs là où une valeur peut manquer — dans le navigateur.
type Root struct {
Orders []Order `json:"orders"`
HasMore bool `json:"has_more"`
}
type Order struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
CreatedAt string `json:"created_at"`
Price float64 `json:"price"`
Coupon *string `json:"coupon"`
GiftNote *string `json:"gift_note,omitzero"`
Shipping Shipping `json:"shipping"`
Tags []any `json:"tags"`
}
type Shipping struct {
City string `json:"city"`
Postcode json.RawMessage `json:"postcode"`
}
À chaque position ci-dessous, chaque nombre a été lu comme un entier, donc son type est int64. Une valeur écrite avec un point décimal ou un exposant, même 10.0, ne s’y décodera pas.
Positions : 2
Order.IDOrder.UserID
À chaque position ci-dessous, les valeurs sont de plus d’une nature — par exemple des nombres et des chaînes —, donc son type est json.RawMessage, qui garde chaque valeur telle qu’elle est écrite dans votre JSON, pour que votre programme la décode une fois qu’il sait de quelle nature elle est. Là où l’une de ces natures est un objet, sa struct figure quand même dans la sortie, pour y décoder cette valeur.
Positions : 1
Shipping.Postcode
À chaque position ci-dessous, il n’y avait rien d’où déduire un type : on n’y a vu que null, le tableau était toujours vide, l’objet n’avait aucune clé, ou la valeur est imbriquée trop profondément pour que cet outil la suive. Son type est donc any, qui accepte n’importe quelle valeur — ou map[string]any là où l’objet n’avait aucune clé, qui accepte n’importe quel objet ou null, et rien d’autre.
Positions : 1
Order.Tags[]
Des structs qui décodent le JSON dont elles sont issues
Collez un échantillon de JSON et cette page écrit pour lui des déclarations Go : une struct nommée pour chaque objet de l’échantillon, et sur chaque champ une balise json qui nomme la clé que ce champ lit. Une promesse décide de tout ce qui suit. Placées dans un programme compilé avec Go 1.27, les déclarations décodent avec "encoding/json", sans erreur, le JSON que vous avez collé, et chaque clé est lue dans un champ, sauf une clé qu’aucune balise de struct ne peut porter, qui est laissée hors de la struct. Là où Go ne permet pas à cette promesse de tenir, ou oblige la page à choisir quelque chose que vos données n’ont pas décidé, un avis sous la sortie dit où, à une exception rare près, décrite dans la section sur les balises.
La forme elle-même est établie avant que la moindre ligne de Go soit écrite, par la même lecture de votre JSON que celle qui alimente la page JSON en TypeScript. Fusionner les éléments d’un tableau, remarquer les clés que seuls certains d’entre eux ont, séparer null d’une clé qui n’a jamais été envoyée et nommer chaque objet trouvé à l’intérieur d’un autre : le guide de cette page traite tout cela, et rien n’en est donc répété plus bas. Ce qui suit est la part de Go — un type par nombre, les pointeurs, un type pour une clé dont les valeurs sont de natures différentes, les noms de champ, les balises, et les questions que posent les avis.
Trois types numériques, choisis selon la façon dont chaque nombre est écrit
Un navigateur qui lit du JSON fait de 10 et de 10.0 le même nombre. Go ne le fait pas : un champ "int64" refuse un nombre écrit avec un point décimal ou un exposant — 10.0, 1e3, même -0.0 —, alors qu’il accepte 11 et -0. La page ne choisit donc pas un type numérique d’après les seules valeurs. Elle demande à l’analyseur du navigateur lui-même comment chaque nombre a été écrit, et décide d’après cela :
- Là où chaque nombre d’une position est écrit comme un nombre entier, sans point décimal ni exposant, le champ est "int64", et l’avis sur les nombres entiers le nomme.
- Tout autre nombre est "float64", si bien qu’un prix écrit 10.0 est "float64" même quand chaque prix de l’échantillon est rond. Le module "json" de Python écrit un float de valeur entière exactement de cette façon, ce qui fait d’une API servie par Python l’endroit habituel où le rencontrer.
- "json.Number" est réservé aux nombres qu’aucun des deux types ne contient exactement : un entier au-delà de l’une ou l’autre extrémité de la plage de "int64", un nombre tout simplement trop grand pour un "float64", comme 1e999, et un entier trop long pour un "float64" à côté d’une fraction, comme dans [9007199254740993, 1.5]. Il garde chacun d’eux exactement, et votre programme le convertit là où il utilise la valeur.
Un long entier parmi d’autres entiers n’a pas besoin d’une telle précaution : [9007199254740993, 1] est "[]int64". Une fraction qui a plus de chiffres qu’un "float64" n’en conserve reste "float64" et est nommée dans un avis qui lui est propre, puisqu’une fois décodé puis encodé à nouveau, 0.30000000000000001 revient sous la forme 0.3.
Lire comment un nombre a été écrit demande un navigateur qui l’indique. Celui qui ne l’indique pas lit chaque nombre d’après sa seule valeur, si bien que 10.0 y paraît entier et que son champ devient "int64", un champ qui ne décodera pas ce JSON ; la page compte les nombres qu’elle n’a pas pu distinguer d’entiers et montre où se trouve le premier. Un tel navigateur ne voit pas non plus les chiffres qu’un "float64" laisse tomber, si bien qu’un long entier à côté d’une fraction y est typé "float64", et qu’une fraction qui s’arrondit n’est pas nommée.
Des pointeurs là où une valeur peut manquer
Une clé que certains objets omettent, ou mettent à null, est un pointeur : "*string", "*int64", ou un pointeur vers la struct écrite pour un objet. Les éléments d’un tableau qui contient null sont eux aussi des pointeurs, si bien que [1, null] est "[]*int64". Une clé omise dans certains objets est en outre marquée "omitzero" dans sa balise. Les deux réunis, un champ peut dire si une valeur est arrivée, et encoder à nouveau la struct décodée omet de nouveau une clé que l’échantillon n’a jamais fait qu’omettre, et réécrit comme null une clé qu’il n’a jamais fait que mettre à null.
Une slice, une map, "any" et "json.RawMessage" ne prennent pas de pointeur, puisque chacun vaut déjà nil quand rien n’y a été décodé. Une slice reçoit tout de même "omitzero" là où sa clé peut manquer, et un tableau présent mais vide est réécrit sous la forme [].
Une distinction ne survit pas. Une clé qui manque dans certains objets et vaut null dans d’autres est un seul nil en Go, si bien que votre programme ne peut pas distinguer les deux cas, et encoder à nouveau la struct omet la clé même là où l’échantillon avait null ; la page nomme chaque champ auquel cela arrive. Une clé aux natures mêlées conserve la différence, parce qu’un "json.RawMessage" garde un null sous la forme des octets null.
json.RawMessage pour les natures mêlées, et any là où rien n’a été vu
Quand les valeurs d’une clé ne sont pas de la même nature — un nombre à un endroit, une chaîne ou un objet à un autre —, le champ est "json.RawMessage" : les octets mêmes de la valeur, laissés à votre programme pour qu’il les décode une fois qu’il a regardé de quelle nature est ce qui est arrivé. Un null parmi elles ne change rien. Là où l’une des natures est un objet, la struct extraite pour lui est quand même affichée à côté des autres. Un "any" accepterait lui aussi toutes les natures, et il n’est pas utilisé pour un mélange, parce qu’un nombre décodé dans un "any" devient un "float64" et perd tout ce qu’un "float64" ne peut pas contenir.
Certaines parties d’un échantillon n’offrent aucune valeur d’où apprendre quoi que ce soit : une clé qui n’a jamais contenu que null, un tableau vide à chaque fois, un objet sans clés, et une valeur enfouie plus profondément que ce que lit l’inférence. En Go, la clé null et la valeur enfouie reçoivent "any", les éléments du tableau vide "[]any" et l’objet "map[string]any". Un "any" accepte toute nature de valeur. La map accepte un objet ou null, et une chaîne, un nombre, un tableau ou un booléen à sa place fait échouer le décodage.
Au-delà de cette profondeur, deux choses ne sont dites par aucun avis. Une clé qui, à cette profondeur, manque dans certains objets et vaut null dans d’autres n’est nommée que comme une position où il n’y avait rien d’où déduire un type, et Go, en la réécrivant, l’omet là où elle valait null. Et un entier plus long que ce que contient un "float64" revient arrondi, 9007199254740993 revenant sous la forme 9007199254740992, parce que la position est un "any".
Des noms de champ dans le style de Go, des noms de struct partagés avec la page TypeScript
Le nom d’un champ est la façon dont Go écrit sa clé. La clé est découpée en mots à chaque caractère qui n’est ni une lettre ni un chiffre, et partout où une lettre minuscule est suivie d’une majuscule ; les mots sont ensuite joints, chacun commençant par une majuscule, si bien que "user_name", "last-name" et "firstName" deviennent UserName, LastName et FirstName. Un mot de la liste de sigles par défaut de staticcheck s’écrit en majuscules — "id" donne ID, "api_key" donne APIKey, "video_url" donne VideoURL —, mais seul un mot entier compte, si bien que "idle" donne Idle et le pluriel "ids" donne Ids. Une clé écrite entièrement en majuscules est lue comme des mots : "USER_ID" donne UserID.
Les lettres propres de la clé sont gardées, si bien que "имя" devient Имя. Là où un nom ne commencerait pas par une majuscule — une clé dans un système d’écriture sans majuscules comme "名前", une clé qui commence par un chiffre comme "1st", ou une première lettre dont la majuscule ne tient pas en une seule lettre, comme "ß" —, il prend un X devant, comme X名前, X1st et Xß, et le champ lit tout de même sa clé. Les marques combinantes sont retirées du nom et gardées dans la balise. Une clé sans aucune lettre ni aucun chiffre, comme "@", s’appelle Field, et un nom déjà utilisé dans la même struct prend un numéro : "user_id", "userId" et "USER_ID" ensemble donnent UserID, UserID2 et UserID3.
Le style de Go s’étend jusqu’aux noms de champ et s’arrête là : chaque struct garde le nom que l’inférence a choisi pour son objet, si bien que les déclarations d’ici et les interfaces de la page TypeScript portent les mêmes noms, et qu’un champ peut s’écrire autrement que la struct qu’il contient. La façon dont ce nom est choisi relève du guide de la page TypeScript.
Des balises pour Go 1.27, et les clés qu’aucune balise ne peut porter
La balise de chaque champ nomme sa clé exactement, comme dans json:"user_id", avec ,omitzero après la clé là où la clé peut manquer. Un caractère de contrôle dans une clé est écrit avec la séquence d’échappement que Go utilise lui-même, et la clé "-" est écrite json:"-,", une forme que Go lit comme cette clé-là.
Les balises sont écrites pour Go 1.27. Go 1.26 lit moins de clés à travers une balise : une clé qui contient autre chose que des lettres, des chiffres, l’espace ASCII et un ensemble de signes de ponctuation ASCII — "Price (€)", "temp °C", un emoji, un caractère de contrôle, un accent combinant — n’y est pas lue, et la page nomme chacun de ces champs. Cette moitié-là est vérifiée sur Go 1.27 compilé avec "GOEXPERIMENT=nojsonv2", qui lit les balises comme Go 1.26 mais avec les tables Unicode plus récentes de Go 1.27, si bien qu’une clé contenant une lettre que les tables plus anciennes de Go 1.26 n’ont pas est lue lors de cette vérification et ne déclenche aucun avis.
Une clé qui contient une virgule, une barre oblique inverse, un guillemet droit, une apostrophe ou un accent grave, ou la clé vide, est une clé qu’aucune balise de struct ne peut porter, et elle ne reçoit donc aucun champ. Votre JSON se décode quand même : "json.Unmarshal" ignore cette clé sans protester, bien qu’un "json.Decoder" réglé sur "DisallowUnknownFields" s’arrête sur elle. L’avis liste une telle clé par sa struct et par la clé citée à la manière dont Go cite une chaîne, comme Order["note,internal"], et un objet placé sous une telle clé a quand même sa struct affichée. Un cas n’est pas signalé : deux clés qui ne diffèrent que par un substitut isolé, une séquence d’échappement qui ne représente aucun caractère, sont une même clé pour Go, et aucun de leurs champs n’est rempli.
Rien que des déclarations, dans la disposition de gofmt
La sortie contient des déclarations de type et rien d’autre : ni clause package ni import, si bien qu’elle se place dans un fichier que vous avez déjà, sous la clause package propre à ce fichier. Là où elle utilise "json.RawMessage" ou "json.Number", le fichier importe aussi "encoding/json", et c’est la ligne qui reste à votre charge ou à celle de votre éditeur. La disposition est celle de gofmt lui-même — une tabulation devant chaque champ, et les noms, types et balises de chaque struct en colonnes alignées —, de sorte que gofmt la laisse exactement telle qu’elle est. Le type de la racine est affiché en premier, chaque objet est un type nommé plutôt qu’une struct écrite à l’intérieur du champ qui la contient, et un JSON qui est un tableau ou une seule valeur au niveau supérieur est lui aussi un type nommé, comme "type Root []RootItem", si bien qu’il y a toujours un type dans lequel le décoder.
Ce que les avis vous demandent de vérifier
Chaque genre de décision imposée par Go reçoit une entrée sous les déclarations, et cette entrée rassemble chaque endroit auquel elle s’applique, si bien qu’un échantillon qui compte beaucoup de champs de nombres entiers reçoit une entrée et non une par champ. Les endroits s’écrivent comme les déclarations les écrivent — Order.UserID est un champ, Order.Tags[] les éléments d’un tableau, et la racine son nom, sans plus —, sauf dans deux entrées qui renvoient plutôt à votre JSON, par ligne et par colonne : une clé écrite deux fois, et les nombres que ce navigateur n’a pas pu distinguer d’entiers. Aucune d’elles ne modifie les déclarations. Lisez chacune comme une question sur vos données :
- Des entiers typés "int64". Chaque nombre à cet endroit a été écrit comme un entier. Si une valeur qui arrive plus tard peut porter un point décimal ou un exposant, comme peuvent le faire un prix ou une mesure, elle ne se décodera pas, donc passez ce champ en "float64" ; un identifiant ou un décompte peut rester tel quel.
- "json.Number". Aucun autre type numérique ne contient ces valeurs exactement. Convertissez chacune là où votre programme l’utilise, ou, si vous savez que les valeurs réelles tiennent dans un type plus étroit, changez le champ vous-même.
- Une fraction que "float64" arrondit. Un nombre à cet endroit a plus de chiffres qu’un "float64" n’en conserve, si bien que votre programme voit une valeur voisine plutôt que celle qui est écrite. Là où chaque chiffre compte, passez le champ en "json.Number".
- Des valeurs de plusieurs natures, gardées en "json.RawMessage". Regardez chaque valeur à son arrivée et décodez-la selon la nature qu’elle se révèle avoir ; là où l’une des natures est un objet, sa struct figure quand même dans la sortie, pour y décoder cette valeur.
- Rien d’où déduire un type. Le champ est "any", ou une map de "any", parce que l’échantillon n’y contenait aucune valeur d’où apprendre quoi que ce soit. Remplacez-le par le type que vous savez être celui de ce champ, ou convertissez à nouveau à partir d’un JSON où il contient de vraies valeurs.
- Une clé qui manque dans certains objets et vaut null dans d’autres. Go garde les deux sous la forme d’un nil, si bien que la clé est omise quand la struct est à nouveau encodée. Cela ne compte que si ce qui lit votre sortie traite un null autrement qu’une clé qui n’est pas là.
- Des clés que Go 1.26 ne lit pas. Compilez avec Go 1.27, ou renommez ces clés là où elles sont produites ; avec Go 1.26, elles restent non lues.
- Des clés laissées hors de la struct. Elles n’ont pas de champ. Pour en lire une, décodez l’objet dans une "map[string]json.RawMessage" ou écrivez une méthode "UnmarshalJSON" pour la struct, comme l’avis lui-même le suggère.
- Une clé écrite deux fois. Les types suivent la dernière copie de la clé, mais Go décode chaque copie tour à tour, si bien qu’une copie antérieure que le type ne peut pas contenir, comme une chaîne là où la dernière copie est un nombre, fait que le décodage renvoie une erreur, et qu’une clé que seule une copie antérieure d’un objet contient n’est pas lue. Corrigez le JSON à la ligne et à la colonne indiquées.
- Ce navigateur ne distingue pas 10 de 10.0. Il n’indique pas comment un nombre est écrit, si bien qu’un champ typé "int64" à cause d’une valeur écrite avec un point décimal ou un exposant ne décodera pas votre JSON. Vérifiez les champs dont les valeurs sont écrites ainsi, ou convertissez le JSON dans un navigateur qui l’indique.
Il n’y a pas d’avis sur les dates, les formats ou les ensembles fixes de valeurs, parce que la page n’en devine jamais un à partir d’une chaîne : "created_at" dans l’exemple reste un "string", quelle que soit son allure.
Questions fréquentes
- Pourquoi mon prix est-il float64 alors que chaque prix de mon JSON est un nombre rond ?
- Parce que chaque prix est écrit avec un point décimal, comme 10.0, et qu’un champ "int64" refuse un nombre écrit ainsi, rond ou non. La page lit comment chaque nombre est écrit plutôt que sa seule valeur, si bien que le champ est "float64" et que votre JSON se décode. Si le champ était typé "int64" d’après les seules valeurs, le décodage s’arrêterait au premier prix.
- Quand un nombre ressort-il en json.Number ?
- Quand l’une de ses valeurs est un entier au-delà de la plage de "int64", un nombre trop grand pour un "float64", ou un long entier placé à côté d’une fraction, qu’un "float64" arrondirait. "json.Number" les garde tous exacts, et l’avis sous la sortie liste chaque champ pour lequel il a été choisi.
- Pourquoi certains champs sont-ils des pointeurs, et que fait omitzero dans les balises ?
- Un champ est un pointeur là où sa clé manque dans certains objets ou vaut null dans certains, pour qu’un nil puisse dire qu’aucune valeur n’est arrivée. "omitzero" se place là où la clé manque dans certains objets : encoder à nouveau la struct omet alors cette clé, comme le faisait votre JSON, au lieu d’écrire null pour elle.
- Pourquoi json.RawMessage pour une clé aux natures mêlées, et pas any ?
- Un "json.RawMessage" garde les octets de chaque valeur jusqu’à ce que votre programme ait décidé comment les lire. Un "any" accepterait lui aussi les valeurs, mais un nombre décodé dans un "any" est un "float64", et un long entier y perd ses derniers chiffres.
- Pourquoi certains noms de champ commencent-ils par un X ?
- Parce que sinon le nom ne commencerait pas par une majuscule : la clé est écrite dans un système d’écriture sans majuscules, commence par un chiffre, ou commence par une lettre dont la majuscule ne tient pas en une seule lettre. Le X garde dans le nom les lettres propres de la clé, et le champ lit toujours sa clé — X名前 lit "名前".
- De quelle version de Go les structs ont-elles besoin ?
- Elles sont écrites et vérifiées pour Go 1.27. La façon dont Go 1.26 lit les balises est vérifiée elle aussi, par l’intermédiaire de ce qui en tient lieu : Go 1.26 ne lit pas une clé contenant un caractère autre que des lettres, des chiffres, l’espace ASCII et un ensemble de signes de ponctuation ASCII, comme un symbole monétaire ou un emoji, et la page nomme chaque champ dont la clé est concernée — sauf une clé contenant une lettre plus récente que les tables Unicode de Go 1.26 : une telle clé, ni la page ni cette vérification ne peuvent la voir.
- Pourquoi l’une de mes clés manque-t-elle dans la struct ?
- Parce que la clé contient une virgule, une barre oblique inverse, un guillemet droit, une apostrophe ou un accent grave, ou qu’elle est vide, et qu’aucune balise de struct ne peut porter une clé pareille. Un avis la liste par sa struct et par la clé elle-même, comme Root["a,b"] ; le reste de votre JSON se décode quand même, et l’avis dit comment lire cette clé autrement.
- Est-il sûr de coller une réponse qui contient des jetons ou des mots de passe ?
- Oui. Chaque étape s’exécute dans cette page, sur votre ordinateur, et la réponse que vous collez ne va nulle part ailleurs : aucun serveur ne la reçoit et rien n’en garde de copie. Les déclarations qui en sortent ne portent que des noms de champ, des balises et des types Go, si bien qu’un jeton ou un mot de passe présent dans le JSON n’y laisse d’autre trace qu’un champ "string" nommé d’après sa clé.
Outils connexes
- JSON en TypeScript
Là où les valeurs d’une clé sont de plus d’une nature, chaque valeur est gardée ici telle qu’elle est écrite dans votre JSON, pour que votre programme la décode, car Go n’a pas d’union. Cette page-là écrit la même forme en types TypeScript, sous les mêmes noms de types, avec une union à cet endroit, et son guide explique comment cette forme est établie.
- JSON en Zod
Go n’a pas de type qui soit simplement un nombre JSON, si bien que là où chaque nombre est écrit comme un entier, le champ reçoit ici un type entier, et une valeur écrite avec un point décimal ne s’y décodera pas. Cette page-là écrit la même forme en schémas Zod, sous les mêmes noms de types, et un schéma vérifie les données chaque fois qu’elles arrivent et accepte aussi une fraction dans un tel champ.
- Testeur JSONPath
Testez des requêtes JSONPath (RFC 9535) sur du JSON.
- Générateur de tableaux Markdown
Créez et alignez des tableaux Markdown depuis CSV, TSV ou JSON.