1. Vue d'ensemble de la bibliothèque standard encoding/json
Le langage Go fournit une puissante bibliothèque encoding/json
pour manipuler le format de données JSON. Avec cette bibliothèque, vous pouvez facilement convertir les types de données Go en format JSON (sérialisation) ou convertir les données JSON en types de données Go (désérialisation). Cette bibliothèque offre de nombreuses fonctionnalités telles que l'encodage, le décodage, les E/S en streaming et la prise en charge de la logique d'analyse JSON personnalisée.
Les types de données et les fonctions les plus importants de cette bibliothèque comprennent :
-
Marshal
etMarshalIndent
: utilisés pour sérialiser les types de données Go en chaînes JSON. -
Unmarshal
: utilisé pour désérialiser les chaînes JSON en types de données Go. -
Encodeur
etDécodeur
: utilisés pour l'E/S en streaming des données JSON. -
Valide
: utilisé pour vérifier si une chaîne donnée est au format JSON valide.
Nous apprendrons spécifiquement l'utilisation de ces fonctions et types dans les chapitres à venir.
2. Serialization des structures de données Go en JSON
2.1 Utilisation de json.Marshal
json.Marshal
est une fonction qui sérialise les types de données Go en chaînes JSON. Elle prend en entrée des types de données du langage Go, les convertit en format JSON, et renvoie une tranche d'octets ainsi que les erreurs éventuelles.
Voici un exemple simple qui montre comment convertir une structure Go en une chaîne JSON :
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{"Alice", 30}
jsonData, err := json.Marshal(person)
if err != nil {
log.Fatalf("Échec du marshaling JSON : %s", err)
}
fmt.Println(string(jsonData)) // Sortie : {"name":"Alice","age":30}
}
En plus des structures, la fonction json.Marshal
peut également sérialiser d'autres types de données tels que les map et les slices. Voici des exemples utilisant map[string]interface{}
et slice
:
// Convertir une map en JSON
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... gestion des erreurs et sortie omises ...
// Convertir une slice en JSON
mySlice := []string{"Pomme", "Banane", "Cerise"}
jsonData, err := json.Marshal(mySlice)
// ... gestion des erreurs et sortie omises ...
2.2 Balises de structure
En Go, les balises de structure sont utilisées pour fournir des métadonnées pour les champs de structure, contrôlant le comportement de la sérialisation JSON. Les cas d'utilisation les plus courants comprennent le renommage des champs, l'ignorance des champs et la sérialisation conditionnelle.
Par exemple, vous pouvez utiliser la balise json:"<name>"
pour spécifier le nom du champ JSON :
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"` // L'ajout de la balise "-" indique que ce champ ne sera pas sérialisé
}
Dans l'exemple ci-dessus, la balise json:"-"
devant le champ Tag
indique à json.Marshal
d'ignorer ce champ. L'option omitempty
pour le champ Description
indique que si le champ est vide (valeur nulle, comme une chaîne vide), il ne sera pas inclus dans le JSON sérialisé.
Voici un exemple complet utilisant des balises de structure :
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
animal := Animal{
SpeciesName: "Éléphant d'Afrique",
Description: "Un grand mammifère avec une trompe et des défenses.",
Tag: "en danger", // Ce champ ne sera pas sérialisé en JSON
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("Échec du marshaling JSON : %s", err)
}
fmt.Println(string(jsonData)) // Sortie : {"species":"Éléphant d'Afrique","desc":"Un grand mammifère avec une trompe et des défenses."}
}
Ainsi, vous pouvez garantir une structure de données claire tout en contrôlant la représentation JSON, gérant de manière flexible divers besoins de sérialisation.
3. Désérialiser JSON en structure de données Go
3.1 Utilisation de json.Unmarshal
La fonction json.Unmarshal
nous permet de parser des chaînes JSON en structures de données Go telles que des structs, des maps, etc. Pour utiliser json.Unmarshal
, nous devons d'abord définir une structure de données Go qui correspond aux données JSON.
Supposons que nous ayons les données JSON suivantes :
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
Pour parser ces données dans une struct Go, nous devons définir une struct correspondante :
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
Maintenant, nous pouvons utiliser json.Unmarshal
pour la désérialisation :
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Erreur lors de la désérialisation JSON:", err)
return
}
fmt.Printf("Utilisateur : %+v\n", user)
}
Dans l'exemple ci-dessus, nous avons utilisé des tags comme json:"name"
pour informer la fonction json.Unmarshal
sur la correspondance des champs JSON avec les champs de la struct.
3.2 Analyse Dynamique
Parfois, la structure JSON que nous devons analyser n'est pas connue à l'avance, ou la structure des données JSON peut changer de manière dynamique. Dans de tels cas, nous pouvons utiliser interface{}
ou json.RawMessage
pour l'analyse.
L'utilisation de interface{}
nous permet de parser sans connaître la structure JSON :
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Ingénieur"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// Assertions de type, vérifiez la correspondance des types avant utilisation
name := result["name"].(string)
fmt.Println("Nom :", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // Remarque : les nombres dans interface{} sont traités comme float64
fmt.Println("Âge :", age)
}
L'utilisation de json.RawMessage
nous permet de conserver le JSON d'origine tout en analysant sélectivement ses parties :
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Ingénieur"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("Nom :", user.Name)
fmt.Println("Âge :", details["age"])
fmt.Println("Fonction :", details["job"])
}
Cette approche est utile pour gérer les structures JSON où certains champs peuvent contenir différents types de données, et permet une manipulation flexible des données.
4 Gestion des Structures et Tableaux Imbriqués
4.1 Objets JSON Imbriqués
Les données JSON courantes ne sont souvent pas plates, mais contiennent des structures imbriquées. En Go, nous pouvons gérer cette situation en définissant des structs imbriquées.
Supposons que nous ayons le JSON imbriqué suivant :
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
Nous pouvons définir la struct Go comme suit :
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
L'opération de désérialisation est similaire aux structures non imbriquées :
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Erreur lors de la désérialisation JSON :", err)
}
fmt.Printf("%+v\n", user)
}
4.2 Tableaux JSON
En JSON, les tableaux sont une structure de données courante. En Go, ils correspondent aux slices.
Considérons le tableau JSON suivant :
[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]
En Go, nous définissons la structure correspondante et la slice comme suit :
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
donnéesJSON := `[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]`
var personnes []Person
json.Unmarshal([]byte(donnéesJSON), &personnes)
for _, personne := range personnes {
fmt.Printf("%+v\n", personne)
}
}
De cette manière, nous pouvons désérialiser chaque élément du tableau JSON en une slice de structures Go pour un traitement ultérieur et un accès.
5 Gestion des Erreurs
Lors de la manipulation de données JSON, qu'il s'agisse de sérialisation (conversion de données structurées en format JSON) ou de désérialisation (conversion de JSON en données structurées), des erreurs peuvent survenir. Ensuite, nous discuterons des erreurs courantes et de la manière de les gérer.
5.1 Gestion des Erreurs de Sérialisation
Les erreurs de sérialisation surviennent généralement lors du processus de conversion d'une structure ou d'autres types de données en une chaîne JSON. Par exemple, si une tentative est faite pour sérialiser une structure contenant des champs illégaux (comme un type de canal ou une fonction qui ne peut pas être représentée en JSON), json.Marshal
renverra une erreur.
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// Supposons qu'il y ait un champ ici qui ne peut pas être sérialisé
// Data chan struct{} // Les canaux ne peuvent pas être représentés en JSON
}
func main() {
u := User{
Name: "Alice",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("Échec de la sérialisation JSON : %v", err)
}
fmt.Println(string(bytes))
}
Dans l'exemple ci-dessus, nous avons intentionnellement commenté le champ Data
. S'il est décommenté, la sérialisation échouera et le programme enregistrera l'erreur et mettra fin à l'exécution. La gestion de telles erreurs implique généralement la vérification des erreurs et la mise en œuvre de stratégies de gestion des erreurs correspondantes (comme l'enregistrement des erreurs, le retour de données par défaut, etc.).
5.2 Gestion des Erreurs de Désérialisation
Des erreurs de désérialisation peuvent survenir lors du processus de conversion d'une chaîne JSON en une structure Go ou tout autre type de données. Par exemple, si le format de la chaîne JSON est incorrect ou incompatible avec le type cible, json.Unmarshal
renverra une erreur.
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"Alice","age":"inconnu"}`) // "age" devrait être un entier, mais une chaîne est fournie ici
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("Échec de la désérialisation JSON : %v", err)
}
fmt.Printf("%+v\n", u)
}
Dans cet exemple de code, nous avons délibérément fourni le mauvais type de données pour le champ age
(une chaîne au lieu de l'entier attendu), ce qui entraîne une erreur de json.Unmarshal
. Par conséquent, nous devons gérer cette situation de manière appropriée. La pratique courante consiste à enregistrer le message d'erreur et, en fonction du scénario, éventuellement renvoyer un objet vide, une valeur par défaut ou un message d'erreur.
6 Fonctionnalités Avancées et Optimisation des Performances
6.1 Marshalisation et démarshalisation personnalisées
Par défaut, le package encoding/json
en Go sérialise et désérialise le JSON via la réflexion. Cependant, nous pouvons personnaliser ces processus en implémentant les interfaces json.Marshaler
et json.Unmarshaler
.
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
Ici, nous avons défini un type Color
et implémenté les méthodes MarshalJSON
et UnmarshalJSON
pour convertir les couleurs RVB en chaînes hexadécimales, puis retourner aux couleurs RVB.
6.2 Codeurs et décodeurs
Lors du traitement de gros volumes de données JSON, l'utilisation directe de json.Marshal
et json.Unmarshal
peut entraîner une consommation excessive de mémoire ou des opérations d'entrée/sortie inefficaces. Par conséquent, le package encoding/json
en Go fournit des types Encoder
et Decoder
, qui peuvent traiter les données JSON de manière itérative.
6.2.1 Utilisation de json.Encoder
json.Encoder
peut écrire directement des données JSON sur n'importe quel objet qui implémente l'interface io.Writer, ce qui signifie que vous pouvez encoder directement des données JSON dans un fichier, une connexion réseau, etc.
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Erreur d'encodage : %v", err)
}
}
6.2.2 Utilisation de json.Decoder
json.Decoder
peut lire directement des données JSON à partir de n'importe quel objet qui implémente l'interface io.Reader, en recherchant et en analysant des objets et des tableaux JSON.
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Erreur de décodage : %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
En traitant les données avec des codeurs et des décodeurs, vous pouvez effectuer un traitement JSON tout en lisant, réduisant l'utilisation de la mémoire et améliorant l'efficacité du traitement, ce qui est particulièrement utile pour la manipulation des transferts réseau ou des fichiers volumineux.