Décodage de JSON à l'aide de json.Unmarshal vs json.NewDecoder.Decode

203

Je développe un client API dans lequel je dois encoder une charge utile JSON sur demande et décoder un corps JSON à partir de la réponse.

J'ai lu le code source de plusieurs bibliothèques et d'après ce que j'ai vu, j'ai essentiellement deux possibilités pour encoder et décoder une chaîne JSON.

Utilisez en json.Unmarshalpassant la chaîne de réponse entière

data, err := ioutil.ReadAll(resp.Body)
if err == nil && data != nil {
    err = json.Unmarshal(data, value)
}

ou en utilisant json.NewDecoder.Decode

err = json.NewDecoder(resp.Body).Decode(value)

Dans mon cas, lorsqu'il s'agit de réponses HTTP qui implémentent io.Reader, la deuxième version semble nécessiter moins de code, mais comme j'ai vu les deux, je me demande s'il y a une préférence si je devrais utiliser une solution plutôt que l'autre.

De plus, la réponse acceptée à cette question dit

Veuillez utiliser à la json.Decoderplace de json.Unmarshal.

mais il n'a pas mentionné la raison. Dois-je vraiment éviter d'utiliser json.Unmarshal?

Simone Carletti
la source
Cette pull request sur GitHub a remplacé un appel à Unmarshal par json.NewDecoder pour «supprimer le tampon dans le décodage JSON».
Matt
Cela dépend simplement de l'entrée qui vous convient le mieux. blog.golang.org/json-and-go donne des exemples d'utilisation des deux techniques.
rexposadas le
15
IMO, ioutil.ReadAllest presque toujours la mauvaise chose à faire. Cela n'est pas lié à votre objectif, mais vous oblige à disposer de suffisamment de mémoire contiguë pour stocker tout ce qui pourrait arriver dans le tuyau, même si les 20 derniers To de réponse sont après le dernier }de votre JSON.
Dustin
@Dustin Vous pouvez utiliser io.LimitReaderpour éviter cela.
Inanc Gumus

Réponses:

240

Cela dépend vraiment de votre contribution. Si vous regardez l'implémentation de la Decodeméthode de json.Decoder, elle met en mémoire tampon toute la valeur JSON en mémoire avant de la démarshalling en une valeur Go. Donc, dans la plupart des cas, la mémoire ne sera plus efficace (bien que cela puisse facilement changer dans une future version du langage).

Donc, une meilleure règle de base est la suivante:

  • À utiliser json.Decodersi vos données proviennent d'un io.Readerflux ou si vous devez décoder plusieurs valeurs à partir d'un flux de données.
  • À utiliser json.Unmarshalsi vous avez déjà les données JSON en mémoire.

Pour le cas de la lecture à partir d'une requête HTTP, je choisirais json.Decodercar vous lisez évidemment à partir d'un flux.

James Henstridge
la source
25
Aussi: en inspectant le code source de Go 1.3, nous pouvons également apprendre que pour l'encodage, si vous utilisez un json.Encoder, il réutilisera un pool de tampons global (soutenu par le nouveau sync.Pool), ce qui devrait beaucoup réduire le taux de désabonnement de la mémoire tampon. si vous encodez beaucoup de json. Il n'y a qu'un seul pool global si différent de json.Encoder le partage. La raison pour laquelle cela n'a pas pu être fait pour l'interface json.Marshal est que les octets sont retournés à l'utilisateur et que l'utilisateur n'a pas le moyen de "renvoyer" les octets au pool. Donc, si vous faites beaucoup d'encodage, json.Marshal a toujours un peu de désabonnement de tampon.
Aktau
@Flimzy: êtes-vous sûr? Le code source dit toujours qu'il lit la valeur entière dans le tampon avant le décodage: github.com/golang/go/blob/master/src/encoding/json/… . La Bufferedméthode est là pour vous permettre de voir toutes les données supplémentaires qui ont été lues dans le tampon interne après la valeur.
James Henstridge
@JamesHenstridge: Non, vous avez probablement raison. J'interprétais simplement votre déclaration différemment de ce que vous vouliez. Toutes mes excuses pour la confusion.
Flimzy