json.Unmarshal vs NewDecoder.Decode : qu'est-ce qui est le plus rapide en Go ?

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Deux frères acrobates : Unmarshal et Decode

\n\n

Si vous codez en Go et travaillez avec du JSON, tôt ou tard vous serez confronté à un choix : utiliser json.Unmarshal ou json.NewDecoder.Decode ? La question est si populaire que sur Stack Overflow, elle a rassemblé des milliers de vues et des dizaines de votes. Mais la réponse que vous y trouverez se résume souvent à "cela dépend de la tâche".

\n\n

Aujourd'hui, nous ne nous contenterons pas d'une réponse abstraite. Nous allons mener notre propre enquête : décortiquer les coulisses des deux méthodes, écrire des benchmarks, montrer des exemples de code concrets et établir des règles claires sur quand utiliser quoi. Attachez vos ceintures, ça va chauffer !

\n\n

L'essentiel en bref : que sont Unmarshal et Decode ?

\n\n

Les deux outils font la même chose : transformer du JSON en structure Go. Mais ils le font différemment.

\n\n
    \n
  • json.Unmarshal — prend en entrée un []byte et un pointeur vers une structure. Tout est immédiat : les octets sont là, la structure est prête, c'est parti. Il fonctionne "de front" — il désérialise l'intégralité des données en mémoire.
  • \n
  • json.NewDecoder.Decode — fonctionne avec un flux (io.Reader). Vous créez un décodeur, vous lui passez un reader (par exemple, os.File ou http.Response.Body) et vous appelez Decode. Le décodeur lit les données par morceaux, sans tout charger en mémoire d'un coup.
  • \n
\n\n

La différence semble évidente : pour les grandes données, utilisez Decoder ; pour les petites, Unmarshal. Mais le diable se cache dans les détails, comme toujours.

\n\n

Dans les coulisses : comment ils fonctionnent sous le capot

\n\n

json.Unmarshal — le tir de sniper

\n\n

Unmarshal est une fonction qui :

\n
    \n
  1. Prend une slice d'octets.
  2. \n
  3. Alloue de la mémoire pour la structure résultante.
  4. \n
  5. En un seul passage, parse tout le JSON et remplit la structure.
  6. \n
\n\n

Tout est simple et élégant. Mais il y a un hic : si vous passez à Unmarshal un gros JSON (par exemple, 100 Mo), vous devez d'abord lire ces 100 Mo en mémoire. Ensuite, Unmarshal allouera encore de la mémoire pour la structure. Résultat : une double consommation de RAM.

\n\n

json.NewDecoder.Decode — le plombier

\n\n

Decoder fonctionne différemment :

\n
    \n
  1. Un nouveau décodeur est créé, qui enveloppe un io.Reader.
  2. \n
  3. Le décodeur lit les données par tampons (par défaut 4 Ko).
  4. \n
  5. Au fur et à mesure de la lecture, il parse le JSON jeton par jeton et remplit la structure.
  6. \n
\n\n

L'avantage : vous ne stockez pas tout le JSON en mémoire. L'inconvénient : un peu plus de frais généraux pour la gestion du tampon et la lecture en flux.

\n\n

Expérience : les benchmarks ne mentent pas

\n\n

Vérifions la théorie en pratique. Écrivons un benchmark simple pour trois scénarios :

\n
    \n
  • Petit JSON (1 Ko)
  • \n
  • JSON moyen (100 Ko)
  • \n
  • Gros JSON (10 Mo)
  • \n
\n\n
package main\n\nimport (\n    "bytes"\n    "encoding/json"\n    "testing"\n)\n\ntype Data struct {\n    ID    int    `json:"id"`\n    Name  string `json:"name"`\n    Items []int  `json:"items"`\n}\n\nfunc generateJSON(size int) []byte {\n    data := Data{\n        ID:    1,\n        Name:  "test",\n        Items: make([]int, size),\n    }\n    for i := 0; i < size; i++ {\n        data.Items[i] = i\n    }\n    b, _ := json.Marshal(data)\n    return b\n}\n\nfunc BenchmarkUnmarshal(b *testing.B) {\n    jsonData := generateJSON(1000) // 1 Ko\n    var result Data\n    for i := 0; i < b.N; i++ {\n        json.Unmarshal(jsonData, &result)\n    }\n}\n\nfunc BenchmarkDecoder(b *testing.B) {\n    jsonData := generateJSON(1000) // 1 Ko\n    var result Data\n    for i := 0; i < b.N; i++ {\n        reader := bytes.NewReader(jsonData)\n        json.NewDecoder(reader).Decode(&result)\n    }\n}
\n\n

Résultats (sur mon ordinateur portable, Go 1.22) :

\n\n\n \n
Taille

Blogs

Book Recommendations