json.Unmarshal vs NewDecoder.Decode: o que é mais rápido em Go?

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Dois irmãos acrobatas: Unmarshal e Decode



Se você programa em Go e trabalha com JSON, mais cedo ou mais tarde se depara com a escolha: usar json.Unmarshal ou json.NewDecoder.Decode? A pergunta é tão popular que no Stack Overflow já acumulou milhares de visualizações e dezenas de votos. Mas a resposta que você encontra lá geralmente se resume a "depende da tarefa".



Hoje não vamos dar apenas uma resposta abstrata. Vamos fazer nossa própria investigação: analisar os bastidores de ambos os métodos, escrever benchmarks, mostrar exemplos reais de código e definir regras claras sobre quando usar cada um. Aperte os cintos — vai esquentar!



Resumindo: o que são Unmarshal e Decode?



Ambas as ferramentas fazem a mesma coisa — transformam JSON em uma estrutura Go. Mas fazem isso de maneiras diferentes.



  • json.Unmarshal — recebe como entrada []byte e um ponteiro para a estrutura. Tudo de uma vez: os bytes estão lá, a estrutura está pronta, vamos nessa. Funciona "diretamente" — desserializa todo o array de dados na memória.
  • json.NewDecoder.Decode — trabalha com fluxo (io.Reader). Você cria um decoder, passa um reader (por exemplo, os.File ou http.Response.Body) e chama Decode. O decoder lê os dados em partes, sem carregar tudo na memória de uma vez.


Parece óbvia a diferença: para dados grandes — Decoder, para dados pequenos — Unmarshal. Mas o diabo, como sempre, está nos detalhes.



Os bastidores: como funcionam internamente



json.Unmarshal — tiro de precisão



Unmarshal é uma função que:

  1. Recebe um slice de bytes.
  2. Aloca memória para a estrutura resultante.
  3. Em uma única passada, faz o parsing de todo o JSON e preenche a estrutura.


Tudo simples e elegante. Mas há um detalhe: se você passar um JSON grande para Unmarshal (por exemplo, 100 MB), primeiro você precisa ler esses 100 MB para a memória. Depois, Unmarshal alocará mais memória para a estrutura. Resultado: consumo duplo de RAM.



json.NewDecoder.Decode — o encanador



O Decoder funciona de forma diferente:

  1. Cria-se um novo decoder que envolve o io.Reader.
  2. O decoder lê os dados em buffers (por padrão, 4 KB).
  3. À medida que lê, ele faz o parsing do JSON token por token e preenche a estrutura.


A vantagem é que você não armazena todo o JSON na memória. A desvantagem é um pouco mais de overhead no gerenciamento do buffer e na leitura em fluxo.



Experimento: benchmarks não mentem



Vamos verificar a teoria na prática. Vamos escrever um benchmark simples para três cenários:

  • JSON pequeno (1 KB)
  • JSON médio (100 KB)
  • JSON grande (10 MB)


package main

import ( "bytes" "encoding/json" "testing")

type Data struct { ID int `json:"id"` Name string `json:"name"` Items []int `json:"items"`}

func generateJSON(size int) []byte { data := Data{ ID: 1, Name: "test", Items: make([]int, size), } for i := 0; i < size; i++ { data.Items[i] = i } b, _ := json.Marshal(data) return b}

func BenchmarkUnmarshal(b *testing.B) { jsonData := generateJSON(1000) // 1 KB var result Data for i := 0; i < b.N; i++ { json.Unmarshal(jsonData, &result) }}

func BenchmarkDecoder(b *testing.B) { jsonData := generateJSON(1000) // 1 KB var result Data for i := 0; i < b.N; i++ { reader := bytes.NewReader(jsonData) json.NewDecoder(reader).Decode(&result) }}


Resultados (no meu notebook, Go 1.22):



Tamanho

Blogs

Book Recommendations