Comment utiliser JSON dans Go

De Get Docs
Aller à :navigation, rechercher

L'auteur a sélectionné le Diversity in Tech Fund pour recevoir un don dans le cadre du programme Write for DOnations.

Introduction

Dans les programmes modernes, il est important de communiquer entre un programme et un autre. Qu'il s'agisse d'un programme Go vérifiant si un utilisateur a accès à un autre programme, d'un programme JavaScript obtenant une liste des commandes passées à afficher sur un site Web, ou d'un programme Rust[X183X ] programme lisant les résultats des tests à partir d'un fichier, les programmes ont besoin d'un moyen de fournir des données à d'autres programmes. Cependant, de nombreux langages de programmation ont leur propre façon de stocker les données en interne que les autres langages ne comprennent pas. Pour permettre à ces langages d'interagir, les données doivent être converties dans un format commun qu'ils peuvent tous comprendre. L'un de ces formats, JSON, est un moyen courant de transmettre des données sur Internet ainsi qu'entre des programmes d'un même système.

De nombreux langages de programmation modernes incluent un moyen de convertir des données vers et depuis JSON dans leurs bibliothèques standard, et Go le fait également. En utilisant le package encoding/json fourni par Go, vos programmes Go pourront également interagir avec tout autre système pouvant communiquer à l'aide de JSON.

Dans ce tutoriel, vous allez commencer par créer un programme qui utilise le package encoding/json pour encoder les données d'un map en données JSON, puis mettre à jour votre programme pour utiliser un type struct pour coder les données à la place. Après cela, vous mettrez à jour votre programme pour décoder les données JSON en un map avant de finalement décoder les données JSON en un type struct.

Conditions préalables

Pour suivre ce tutoriel, vous aurez besoin de :

Utilisation d'une carte pour générer du JSON

La prise en charge de Go pour l'encodage et le décodage JSON est fournie par le package encoding/json de la bibliothèque standard. La première fonction que vous utiliserez à partir de ce package est la fonction json.Marshal. Marshalling, parfois également appelé sérialisation, est le processus de transformation des données de programme en mémoire dans un format qui peut être transmis ou enregistré ailleurs. La fonction json.Marshal est alors utilisée pour convertir les données Go en données JSON. La fonction json.Marshal accepte un type interface{} comme valeur à marshaler vers JSON, de sorte que toute valeur peut être transmise en tant que paramètre et renverra les données JSON en conséquence. Dans cette section, vous allez créer un programme à l'aide de la fonction json.Marshal pour générer du JSON contenant divers types de données à partir des valeurs Go map, puis imprimer ces valeurs dans la sortie.

La plupart des JSON sont représentés comme un objet, avec une clé string et divers autres types comme valeurs. Pour cette raison, le moyen le plus flexible de générer des données JSON dans Go consiste à placer des données dans un map à l'aide de clés string et de valeurs interface{}. La clé string peut être directement traduite en une clé d'objet JSON, et la valeur interface{} permet à la valeur d'être n'importe quelle autre valeur, qu'il s'agisse d'un string, d'un int, ou même un autre map[string]interface{}.

Pour commencer à utiliser le package encoding/json dans un programme, vous devez disposer d'un répertoire pour le programme. Dans ce didacticiel, vous utiliserez un répertoire nommé projects.

Commencez par créer le répertoire projects et accédez-y :

mkdir projects
cd projects

Ensuite, créez le répertoire de votre projet. Dans ce cas, utilisez le répertoire jsondata :

mkdir jsondata
cd jsondata

Dans le répertoire jsondata, utilisez nano, ou votre éditeur préféré, pour ouvrir le fichier main.go :

nano main.go

Dans le fichier main.go, vous ajouterez une fonction main pour exécuter votre programme. Ensuite, vous allez ajouter une valeur map[string]interface{} avec différentes clés et types de données. Ensuite, vous utiliserez la fonction json.Marshal pour rassembler les données map en données JSON.

Ajoutez les lignes suivantes à main.go :

main.go

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    data := map[string]interface{}{
        "intValue":    1234,
        "boolValue":   true,
        "stringValue": "hello!",
        "objectValue": map[string]interface{}{
            "arrayValue": []int{1, 2, 3, 4},
        },
    }

    jsonData, err := json.Marshal(data)
    if err != nil {
        fmt.Printf("could not marshal json: %s\n", err)
        return
    }

    fmt.Printf("json data: %s\n", jsonData)
}

Vous verrez dans la variable data que chaque valeur a un string comme clé, mais les valeurs de ces clés varient. L'une est une valeur int, une autre est une valeur bool, et une est même une autre valeur map[string]interface{} avec une valeur []int à l'intérieur.

Lorsque vous transmettez la variable data à json.Marshal, la fonction examine toutes les valeurs que vous avez fournies et détermine de quel type elles sont et comment les représenter dans JSON. S'il y a des problèmes dans la traduction, la fonction json.Marshal renverra un error décrivant le problème. Si la traduction réussit, cependant, la variable jsonData contiendra un []byte des données JSON rassemblées. Comme une valeur []byte peut être convertie en une valeur string en utilisant myString := string(jsonData), ou le %s verb dans une chaîne de format, vous peut ensuite imprimer les données JSON à l'écran en utilisant fmt.Printf.

Enregistrez et fermez le fichier.

Pour voir la sortie de votre programme, utilisez la commande go run et fournissez le fichier main.go :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

Dans la sortie, vous verrez que la valeur JSON de niveau supérieur est un objet représenté par des accolades ({}) qui l'entourent. Toutes les valeurs que vous avez incluses dans data sont présentes. Vous verrez également que objectValue map[string]interface{} a été traduit en un autre objet JSON entouré de {}, et inclut également arrayValue à l'intérieur avec la valeur du tableau de [1,2,3,4].

Temps d'encodage en JSON

Cependant, le package encoding/json ne prend pas uniquement en charge des types tels que les valeurs string et int. Il peut également coder des types plus complexes. L'un des types les plus complexes pris en charge est le type time.Time du package time.

Remarque : Pour en savoir plus sur le package time de Go, consultez le didacticiel Comment utiliser les dates et heures dans Go.


Pour voir cela en action, ouvrez à nouveau votre fichier main.go et ajoutez une valeur time.Time à vos données à l'aide de la fonction time.Date :

main.go

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

func main() {
    data := map[string]interface{}{
        "intValue":    1234,
        "boolValue":   true,
        "stringValue": "hello!",
        "dateValue":   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
        "objectValue": map[string]interface{}{
            "arrayValue": []int{1, 2, 3, 4},
        },
    }

    ...
}

Cette mise à jour attribuera la date March 2, 2022, et l'heure 9:10:00 AM dans le fuseau horaire UTC à la touche dateValue.

Une fois que vous avez enregistré vos modifications, exécutez à nouveau votre programme avec la même commande go run qu'avant :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

Cette fois dans la sortie, vous verrez un champ dateValue dans les données JSON avec l'heure formatée à l'aide du format RFC 3339, un format commun utilisé pour transmettre les dates et les heures comme [X181X ] valeurs.

Encodage des valeurs null en JSON

Selon les systèmes avec lesquels votre programme interagit, vous devrez peut-être envoyer des valeurs null dans vos données JSON, et le package encoding/json de Go peut également gérer cela pour vous. En utilisant un map, il suffit d'ajouter de nouvelles clés string avec une valeur nil.

Pour ajouter quelques valeurs null à votre sortie JSON, ouvrez à nouveau votre fichier main.go et ajoutez les lignes suivantes :

main.go

...

func main() {
    data := map[string]interface{}{
        "intValue":    1234,
        "boolValue":   true,
        "stringValue": "hello!",
                "dateValue":   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
        "objectValue": map[string]interface{}{
            "arrayValue": []int{1, 2, 3, 4},
        },
        "nullStringValue": nil,
        "nullIntValue":    nil,
    }

    ...
}

Les valeurs que vous avez ajoutées aux données ont des clés indiquant qu'il s'agit d'une valeur string ou d'une valeur int, mais il n'y a en fait rien dans le code qui en fait l'une ou l'autre de ces valeurs. Étant donné que map a des valeurs interface{}, tout ce que le code sait, c'est que la valeur interface{} est nil. Étant donné que vous n'utilisez ce map que pour convertir des données Go en données JSON, la distinction à ce stade ne fait aucune différence.

Une fois que vous avez enregistré vos modifications dans main.go, exécutez votre programme en utilisant go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"nullIntValue":null,"nullStringValue":null,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

Maintenant, dans la sortie, vous verrez que les champs nullIntValue et nullStringValue sont inclus avec une valeur JSON null. De cette façon, vous pouvez toujours utiliser une valeur map[string]interface{} pour traduire les données Go en données JSON avec les champs attendus.

Dans cette section, vous avez créé un programme capable de marshaler une valeur map[string]interface{} dans des données JSON. Ensuite, vous avez ajouté un champ time.Time aux données et également inclus une paire de champs de valeur null.

Bien que l'utilisation d'un map[string]interface{} pour marshaler les données JSON puisse être très flexible, cela peut également devenir un problème si vous devez envoyer les mêmes données à plusieurs endroits. Si vous copiez ces données à plusieurs endroits dans votre code, il peut être facile de mal saisir accidentellement un nom de champ ou d'affecter des données incorrectes à un champ. Dans de tels cas, il peut être avantageux d'utiliser un type struct pour représenter les données que vous convertissez en JSON.

Utiliser une structure pour générer du JSON

L'un des avantages de l'utilisation d'un langage typé statiquement comme Go est que vous pouvez utiliser ces types pour laisser le compilateur vérifier ou appliquer la cohérence dans vos programmes. Le package encoding/json de Go vous permet d'en tirer parti en définissant un type struct pour représenter les données JSON. Vous pouvez contrôler la manière dont les données contenues dans le struct sont traduites à l'aide des struct tags. Dans cette section, vous allez mettre à jour votre programme pour utiliser un type struct au lieu d'un type map pour générer vos données JSON.

Lorsque vous utilisez un struct pour définir des données JSON, les noms de champ (et non le nom de type struct lui-même) que vous souhaitez traduire doivent être exportés, ce qui signifie qu'ils doivent commencer par une lettre majuscule, par exemple comme IntValue, ou le package encoding/json ne pourra pas accéder aux champs pour les traduire en JSON. Si vous n'utilisez pas de balises struct pour contrôler la dénomination de ces champs, les noms de champs seront traduits directement tels qu'ils sont sur le struct. L'utilisation des noms par défaut peut être ce que vous souhaitez dans vos données JSON, selon la façon dont vous souhaitez que vos données soient formées. Si tel est le cas, vous n'aurez pas besoin d'ajouter de balises struct. Cependant, de nombreux consommateurs JSON utilisent des formats de nom tels que intValue ou int_value pour leurs noms de champ, donc l'ajout de ces balises struct vous permettra de contrôler la façon dont cette traduction se produit.

Par exemple, supposons que vous ayez un struct avec un champ appelé IntValue que vous avez marshallé vers JSON :

type myInt struct {
    IntValue int
}

data := &myInt{IntValue: 1234}

Si vous avez rassemblé la variable data en JSON à l'aide de la fonction json.Marshal, vous obtiendrez la valeur suivante :

{"IntValue":1234}

Cependant, si votre consommateur JSON s'attend à ce que le champ soit nommé intValue au lieu de IntValue, vous aurez besoin d'un moyen de dire encoding/json. Étant donné que json.Marshal ne sait pas comment vous vous attendez à ce que le champ soit nommé dans les données JSON, vous le direz en ajoutant une balise struct au champ. En ajoutant une balise de structure json au champ IntValue avec une valeur de intValue, vous indiquez à json.Marshal qu'il doit utiliser le nom intValue lors de la génération des données JSON :

type myInt struct {
    IntValue int `json:"intValue"`
}

data := &myInt{IntValue: 1234}

Cette fois, si vous marshalez la variable data à JSON, la fonction json.Marshal verra la balise struct json et saura nommer le champ intValue, donc vous 'll obtenir votre résultat attendu:

{"intValue":1234}

Maintenant, vous allez mettre à jour votre programme pour utiliser une valeur struct pour vos données JSON. Vous allez ajouter un type myJSON struct pour définir votre objet JSON de niveau supérieur, ainsi qu'un myObject struct pour définir votre objet JSON interne pour le champ ObjectValue. Vous ajouterez également une balise de structure json à chacun des champs pour indiquer à json.Marshal comment les nommer dans les données JSON. Vous devrez également mettre à jour l'affectation de la variable data pour utiliser votre structure myJSON, en la déclarant comme vous le feriez pour n'importe quel autre Go struct.

Ouvrez votre fichier main.go et apportez les modifications suivantes :

main.go

...

type myJSON struct {
    IntValue        int       `json:"intValue"`
    BoolValue       bool      `json:"boolValue"`
    StringValue     string    `json:"stringValue"`
    DateValue       time.Time `json:"dateValue"`
    ObjectValue     *myObject `json:"objectValue"`
    NullStringValue *string   `json:"nullStringValue"`
    NullIntValue    *int      `json:"nullIntValue"`
}

type myObject struct {
    ArrayValue []int `json:"arrayValue"`
}

func main() {
    otherInt := 4321
    data := &myJSON{
        IntValue:    1234,
        BoolValue:   true,
        StringValue: "hello!",
        DateValue:   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
        ObjectValue: &myObject{
            ArrayValue: []int{1, 2, 3, 4},
        },
        NullStringValue: nil,
        NullIntValue:    &otherInt,
    }

    ...
}

Beaucoup de ces changements sont similaires à l'exemple de nom de champ IntValue d'avant, mais certains des changements méritent d'être signalés spécifiquement. L'un d'eux, le champ ObjectValue, utilise un type de référence de *myObject pour dire au marshaller JSON d'attendre soit une référence à une valeur myObject ou un [ X160X] valeur. C'est ainsi que vous pouvez définir un objet JSON composé de plusieurs couches d'objets personnalisés. Si vos données JSON l'exigent, vous pouvez également avoir un autre type struct référencé dans le type myObject, et ainsi de suite. En utilisant ce modèle, vous pouvez décrire des objets JSON très complexes à l'aide des types Go struct.

Une autre paire de champs à examiner dans le code ci-dessus sont NullStringValue et NullIntValue. Contrairement à StringValue et IntValue, les types de ces valeurs sont des types de référence *string et *int. Par défaut, les types string et int ne peuvent pas avoir une valeur de nil puisque leurs valeurs « vides » sont "" et 0. Donc, si vous voulez représenter un champ qui peut être d'un type ou nil, vous devez en faire une référence. Par exemple, imaginez que vous ayez un questionnaire utilisateur et que vous vouliez pouvoir représenter si un utilisateur a choisi de ne pas répondre à la question (une valeur null), ou si l'utilisateur n'a pas eu de réponse à la question ( une valeur "").

Ce code met également à jour le champ NullIntValue pour lui attribuer une valeur de 4321 afin de montrer comment vous pouvez attribuer une valeur à un type de référence tel que *int. Dans Go, vous ne pouvez créer des références qu'aux types primitifs, tels que int et string, à l'aide de variables. Ainsi, pour affecter une valeur au champ NullIntValue, vous affectez d'abord la valeur à une autre variable, otherInt, puis obtenez une référence à celle-ci en utilisant &otherInt (au lieu de faire &4321 directement).

Une fois vos mises à jour enregistrées, exécutez votre programme en utilisant go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321}

Vous verrez que cette sortie est la même que lorsque vous avez utilisé une valeur map[string]interface{}, sauf que cette fois nullIntValue a une valeur de 4321 car c'est la valeur de [ X155X].

Initialement, la configuration de vos valeurs struct peut prendre un peu plus de temps, mais une fois que vous les avez définies, vous pouvez les utiliser encore et encore dans votre code, et le résultat sera le même, peu importe où vous les utilisez. eux. Vous pouvez également les mettre à jour en un seul endroit au lieu d'essayer de trouver tous les endroits où un map peut être utilisé à la place.

Le marshaller JSON de Go vous permet également de contrôler si un champ doit être inclus dans la sortie JSON selon que la valeur est vide ou non. Parfois, vous pouvez avoir un objet JSON volumineux ou des champs facultatifs que vous ne souhaitez pas inclure tout le temps, il peut donc être utile d'omettre ces champs. Contrôler si un champ est omis lorsqu'il est vide ou non se fait via l'option omitempty dans la balise struct json.

Maintenant, mettez à jour votre programme pour faire du champ NullStringValue omitempty et ajoutez un nouveau champ nommé EmptyString avec la même option :

main.go

...

type myJSON struct {
    ...
    
    NullStringValue *string   `json:"nullStringValue,omitempty"`
    NullIntValue    *int      `json:"nullIntValue"`
    EmptyString     string    `json:"emptyString,omitempty"`
}

...

Désormais, lorsque myJSON est trié, les champs EmptyString et NullStringValue seront exclus de la sortie si leurs valeurs sont vides.

Après avoir enregistré vos modifications, exécutez votre programme en utilisant go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321}

Cette fois dans la sortie, vous verrez que le champ nullStringValue n'apparaît plus. Puisqu'il est considéré comme vide en ayant une valeur nil, l'option omitempty l'a exclu de la sortie. Vous verrez également que le nouveau champ emptyString n'est pas inclus non plus. Même si la valeur emptyString n'est pas nil, la valeur par défaut "" pour une chaîne est considérée comme vide, elle a donc également été exclue.

Dans cette section, vous avez mis à jour votre programme pour utiliser les types struct afin de générer des données JSON avec json.Marshal au lieu d'un type map. Vous avez également mis à jour votre programme pour omettre les champs vides de votre sortie JSON.

Cependant, pour que vos programmes s'intègrent bien dans l'écosystème JSON, vous devez faire plus que simplement générer des données JSON. Vous devrez également être en mesure de lire les données JSON envoyées en réponse à vos demandes, ou d'autres systèmes vous envoyant des demandes. Le package encoding/json fournit également un moyen de décoder les données JSON en différents types Go. Dans la section suivante, vous mettrez à jour votre programme pour décoder une chaîne JSON en un type Go map.

Analyser JSON à l'aide d'une carte

Semblable à la première section de ce didacticiel, où vous avez utilisé un map[string]interface{} comme moyen flexible de générer des données JSON, vous pouvez également l'utiliser comme moyen flexible de lire des données JSON. La fonction json.Unmarshal, essentiellement l'opposé de la fonction json.Marshal, prendra les données JSON et les retraduira en données Go. Vous fournissez json.Unmarshal avec les données JSON ainsi que la variable Go dans laquelle placer les données non triées et il renverra soit une valeur error s'il est incapable de le faire, soit un [ X181X] valeur d'erreur si elle a réussi. Dans cette section, vous allez mettre à jour votre programme pour utiliser la fonction json.Unmarshal afin de lire les données JSON d'une valeur prédéfinie string dans une variable map. Vous mettrez également à jour votre programme pour imprimer les données Go sur la sortie.

Maintenant, mettez à jour votre programme pour utiliser json.Unmarshal pour démarshaler les données JSON vers un map[string]interface{}. Vous commencerez par remplacer votre variable data d'origine par une variable jsonData contenant une chaîne JSON. Ensuite, vous déclarerez une nouvelle variable data en tant que map[string]interface{} pour recevoir les données JSON. Et enfin, vous utiliserez json.Unmarshal avec ces variables pour accéder aux données JSON.

Ouvrez votre fichier main.go et remplacez les lignes de votre fonction main par ce qui suit :

main.go

...

func main() {
    jsonData := `
        {
            "intValue":1234,
            "boolValue":true,
            "stringValue":"hello!",
            "dateValue":"2022-03-02T09:10:00Z",
            "objectValue":{
                "arrayValue":[1,2,3,4]
            },
            "nullStringValue":null,
            "nullIntValue":null
        }
    `

    var data map[string]interface{}
    err := json.Unmarshal([]byte(jsonData), &data)
    if err != nil {
        fmt.Printf("could not unmarshal json: %s\n", err)
        return
    }

    fmt.Printf("json map: %v\n", data)
}

Dans cette mise à jour, la variable jsonData est définie à l'aide d'un littéral de chaîne brute [1] pour permettre à la déclaration de s'étendre sur plusieurs lignes pour une lecture plus facile. Après avoir déclaré data en tant que map[string]interface{}, vous passez jsonData et data à json.Unmarshal pour démarshaler les données JSON dans le [ X128X].

La variable jsonData est transmise à json.Unmarshal en tant que []byte car la fonction nécessite un type []byte et jsonData est initialement défini comme un Type string. Cela fonctionne car un string dans Go peut être traduit en []byte, et vice versa. La variable data est transmise comme référence car pour que json.Unmarshal place des données dans la variable, il doit avoir une référence à l'endroit où la variable est stockée en mémoire.

Enfin, une fois que les données JSON ont été désorganisées dans la variable data, vous les imprimez à l'écran en utilisant fmt.Printf.

Pour exécuter votre programme mis à jour, enregistrez vos modifications et exécutez le programme à l'aide de go run :

go run main.go

La sortie ressemblera à ceci :

Outputjson map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]

Cette fois, votre sortie affiche le côté Go de la traduction JSON. Vous avez une valeur map, avec les différents champs des données JSON inclus. Vous verrez que même les champs null des données JSON s'affichent sur la carte.

Maintenant, parce que vos données Go sont dans un map[string]interface{}, il y a un peu de travail à faire pour utiliser les données. Vous devez obtenir la valeur du map en utilisant la valeur de clé string souhaitée, puis vous devez vous assurer que la valeur que vous avez reçue est celle que vous attendiez car elle vous est renvoyée sous forme de [ Valeur X199X].

Pour cela, ouvrez le fichier main.go et mettez à jour votre programme pour lire le champ dateValue avec le code suivant :

main.go

...

func main() {
    ...
    
    fmt.Printf("json map: %v\n", data)

    rawDateValue, ok := data["dateValue"]
    if !ok {
        fmt.Printf("dateValue does not exist\n")
        return
    }
    dateValue, ok := rawDateValue.(string)
    if !ok {
        fmt.Printf("dateValue is not a string\n")
        return
    }
    fmt.Printf("date value: %s\n", dateValue)
}

Dans cette mise à jour, vous utilisez data["dateValue"] pour obtenir le rawDateValue en tant que type interface{}, et utilisez la variable ok pour vous assurer que le dateValue ] se trouve dans le champ map.

Ensuite, vous utilisez une assertion de type [2] pour affirmer que le type de rawDateValue est en fait une valeur string, et l'affectez à la variable dateValue. Après cela, vous utilisez à nouveau la variable ok pour vous assurer que l'assertion a réussi.

Enfin, vous utilisez fmt.Printf pour imprimer dateValue.

Pour exécuter à nouveau votre programme mis à jour, enregistrez vos modifications et exécutez-le à l'aide de go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]
date value: 2022-03-02T09:10:00Z

Vous pouvez voir la ligne date value montrant le champ dateValue extrait du map et converti en une valeur string.

Dans cette section, vous avez mis à jour votre programme pour utiliser la fonction json.Unmarshal avec une variable map[string]interface{} pour démarshaler les données JSON dans les données Go. Ensuite, vous avez mis à jour le programme pour extraire la valeur de dateValue des données Go et l'imprimer à l'écran.

Cependant, cette mise à jour montre l'un des inconvénients de l'utilisation d'un map[string]interface{} pour démarshaler JSON dans Go. Étant donné que Go ne sait pas à quel type de données appartient chaque champ (la seule chose qu'il sait, c'est qu'il s'agit d'un interface{}), le mieux qu'il puisse faire pour démarshaler les données est de faire une meilleure estimation. Cela signifie que les valeurs complexes telles que time.Time pour le champ dateValue ne peuvent pas être désorganisées pour vous et ne sont accessibles qu'en tant que string. Un problème similaire se produit si vous essayez d'accéder à n'importe quelle valeur numérique dans un map de cette façon. Étant donné que json.Unmarshal ne sait pas si le nombre doit être un int, un float, un int64, etc., la meilleure estimation qu'il peut faire est de le mettre dans le type de nombre le plus flexible disponible, un float64.

Bien que l'utilisation d'un map pour décoder les données JSON puisse être flexible, cela vous laisse également plus de travail lors de l'interprétation des données dont vous disposez. De la même manière que la fonction json.Marshal peut utiliser des valeurs struct pour générer des données JSON, la fonction json.Unmarshal peut utiliser des valeurs struct pour lire des données JSON. Cela peut aider à supprimer les complexités d'assertion de type liées à l'utilisation d'un map en utilisant les définitions de type sur les champs du struct pour déterminer les types avec lesquels les données JSON doivent être interprétées. Dans la section suivante, vous mettrez à jour votre programme pour utiliser les types struct afin de supprimer ces complexités.

Analyser JSON à l'aide d'une structure

Lorsque vous lisez des données JSON, il y a de fortes chances que vous connaissiez déjà la structure des données que vous recevez ; sinon, ce serait difficile à interpréter. Vous pouvez utiliser cette connaissance de la structure pour donner à Go des indications sur l'apparence de vos données et le type de données que vous attendez.

Dans une section précédente, vous avez défini les valeurs myJSON et myObject struct et ajouté des balises de structure json pour que Go sache comment nommer les champs lors de la génération de JSON. . Vous pouvez maintenant utiliser ces mêmes valeurs struct pour décoder la chaîne JSON que vous utilisiez, ce qui peut être bénéfique pour réduire le code dupliqué dans votre programme si vous rassemblez et désassemblez les mêmes données JSON. Un autre avantage de l'utilisation d'un struct pour le déclassement des données JSON est que vous pouvez indiquer à Go le type de données attendu pour chaque champ. Enfin, vous bénéficiez également de l'utilisation du compilateur de Go pour vérifier que vous utilisez les noms corrects sur les champs au lieu de manquer potentiellement une faute de frappe dans les valeurs string que vous utiliseriez avec une valeur map .

Maintenant, ouvrez votre fichier main.go et mettez à jour la déclaration de variable data pour utiliser une référence au myJSON struct, et ajoutez quelques fmt.Printf ] lignes pour afficher les données de différents champs sur myJSON :

main.go

...

func main() {
    ...
    
    var data *myJSON
    err := json.Unmarshal([]byte(jsonData), &data)
    if err != nil {
        fmt.Printf("could not unmarshal json: %s\n", err)
        return
    }

    fmt.Printf("json struct: %#v\n", data)
    fmt.Printf("dateValue: %#v\n", data.DateValue)
    fmt.Printf("objectValue: %#v\n", data.ObjectValue)
}

Comme vous avez précédemment défini les types struct, vous n'aurez qu'à mettre à jour le type du champ data pour prendre en charge le démarquage vers un struct. Le reste des mises à jour affiche certaines des données du struct lui-même.

Maintenant, enregistrez vos mises à jour et exécutez votre programme en utilisant go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x1400011c180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""}
dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC)
objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}}

Il y a quelques choses à remarquer dans la sortie cette fois. Vous verrez à la fois dans la ligne json struct ainsi que dans la ligne dateValue que la valeur de date dans vos données JSON a maintenant été convertie en une valeur time.Time (le [X155X Le format ] est ce qui s'affiche lorsque %#v est utilisé comme verbe de format). Étant donné que Go a pu voir le type time.Time sur le champ DateValue de myJSON, il a également pu analyser la valeur string pour vous également.

L'autre chose à noter est que EmptyString apparaît sur la ligne json struct même s'il n'était pas inclus dans les données JSON d'origine. Si un champ est inclus sur un struct utilisé pour le démarshalling JSON et n'est pas inclus dans les données JSON en cours de démarshalling, ce champ est simplement défini sur la valeur par défaut de son type et ignoré. De cette façon, vous pouvez définir en toute sécurité tous les champs possibles que vos données JSON peuvent avoir sans vous soucier d'obtenir une erreur si un champ n'existe pas de chaque côté du processus. NullStringValue et NullIntValue sont également définis sur leur valeur par défaut de nil car les données JSON indiquaient que leurs valeurs étaient null, mais ils seraient également définis sur nil si ces champs avaient été exclus des données JSON.

Semblable à la façon dont le champ EmptyString sur votre struct a été ignoré par json.Unmarshal lorsque le champ emptyString était absent des données JSON, l'inverse est également vrai. Si un champ est inclus dans les données JSON mais n'a pas de champ correspondant sur le Go struct, ce champ JSON est ignoré et l'analyse se poursuit avec le champ JSON suivant. De cette façon, si les données JSON que vous lisez sont très volumineuses et que votre programme ne se soucie que d'un petit nombre de ces champs, vous pouvez choisir de créer un struct qui n'inclut que les champs qui vous intéressent. Tous les champs inclus dans les données JSON qui ne sont pas définis sur le struct sont simplement ignorés et l'analyseur JSON de Go continuera avec le champ suivant.

Pour voir cela en action, ouvrez votre fichier main.go une dernière fois et mettez à jour le jsonData pour inclure un champ qui n'est pas inclus sur myJSON :

main.go

...

func main() {
    jsonData := `
        {
            "intValue":1234,
            "boolValue":true,
            "stringValue":"hello!",
            "dateValue":"2022-03-02T09:10:00Z",
            "objectValue":{
                "arrayValue":[1,2,3,4]
            },
            "nullStringValue":null,
            "nullIntValue":null,
            "extraValue":4321
        }
    `

    ...
}

Une fois que vous avez ajouté les données JSON, enregistrez votre fichier et exécutez-le en utilisant go run :

go run main.go

Votre sortie ressemblera à ceci :

Outputjson struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x14000126180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""}
dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC)
objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}}

Vous ne devriez voir aucune différence entre cette sortie et la sortie précédente car Go aura ignoré le champ extraValue dans les données JSON et continué.

Dans cette section, vous avez mis à jour votre programme pour utiliser les types struct que vous avez précédemment définis pour démarshaler vos données JSON. Vous avez vu comment Go a pu analyser une valeur time.Time pour vous et ignorer le champ EmptyString défini sur le type struct mais pas dans les données JSON. Vous avez également ajouté un champ supplémentaire aux données JSON pour voir que Go continuera à analyser les données en toute sécurité même si vous ne définissez qu'un sous-ensemble des champs dans les données JSON.

Conclusion

Dans ce didacticiel, vous avez créé un nouveau programme pour utiliser le package encoding/json dans la bibliothèque standard de Go. Tout d'abord, vous avez utilisé la fonction json.Marshal avec un type map[string]interface{} pour créer des données JSON de manière flexible. Ensuite, vous avez mis à jour votre programme pour utiliser les types struct avec les balises struct json pour générer des données JSON de manière cohérente et fiable avec json.Marshal. Après cela, vous avez utilisé la fonction json.Unmarshal avec un type map[string]interface{} pour décoder une chaîne JSON en données Go. Enfin, vous avez utilisé les types struct que vous aviez précédemment définis avec la fonction json.Unmarshal pour laisser Go faire l'analyse et les conversions de type pour vous en fonction de ces champs struct.

En utilisant le package encoding/json, vous pourrez interagir avec de nombreuses API disponibles sur Internet pour créer vos propres intégrations avec des sites Web populaires. Vous pourrez également convertir les données Go de vos propres programmes dans un format que vous pourrez enregistrer puis charger plus tard pour continuer là où le programme s'est arrêté.

En plus des fonctions que vous avez utilisées dans ce didacticiel, le package encoding/json inclut d'autres fonctions et types utiles qui peuvent être utilisés pour interagir avec JSON. La fonction json.MarshalIndent, par exemple, peut être utilisée pour pretty print JSON data pour le dépannage.

Ce tutoriel fait également partie de la série DigitalOcean Comment coder dans Go. La série couvre un certain nombre de sujets Go, de l'installation de Go pour la première fois à l'utilisation du langage lui-même.