Decodificando JSON usando json.Unmarshal vs json.NewDecoder.Decode

201

Estou desenvolvendo um cliente de API em que preciso codificar uma carga JSON mediante solicitação e decodificar um corpo JSON da resposta.

Eu li o código fonte de várias bibliotecas e, pelo que vi, tenho basicamente duas possibilidades para codificar e decodificar uma string JSON.

Use json.Unmarshalpassar toda a cadeia de resposta

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

ou usando json.NewDecoder.Decode

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

No meu caso, ao lidar com respostas HTTP implementadas io.Reader, a segunda versão parece exigir menos código, mas desde que eu vi as duas, fico imaginando se há alguma preferência se devo usar uma solução em vez da outra.

Além disso, a resposta aceita desta pergunta diz

Por favor use em json.Decodervez de json.Unmarshal.

mas não mencionou o motivo. Devo realmente evitar o uso json.Unmarshal?

Simone Carletti
fonte
Essa solicitação de recebimento no GitHub substituiu uma chamada para Unmarshal por json.NewDecoder para "remover o buffer na decodificação JSON".
Matt
Depende apenas de qual entrada é mais conveniente para você usar. blog.golang.org/json-and-go fornece exemplos de uso das duas técnicas.
Rexposadas
15
IMO, ioutil.ReadAllé quase sempre a coisa errada a fazer. Não está relacionado ao seu objetivo, mas exige que você tenha memória contígua suficiente para armazenar o que estiver vindo do canal, mesmo que os últimos 20 TB de resposta sejam os seguintes }no JSON.
Dustin
@Dustin Você pode usar io.LimitReaderpara evitar isso.
Inanc Gumus

Respostas:

239

Realmente depende de qual é a sua entrada. Se você observar a implementação do Decodemétodo json.Decoder, ele armazena em buffer todo o valor JSON na memória antes de descompactá-lo para um valor Go. Portanto, na maioria dos casos, não será mais eficiente em termos de memória (embora isso possa mudar facilmente em uma versão futura do idioma).

Portanto, uma melhor regra geral é esta:

  • Use json.Decoderse seus dados vierem de um io.Readerfluxo ou se você precisar decodificar vários valores a partir de um fluxo de dados.
  • Use json.Unmarshalse você já possui os dados JSON na memória.

No caso de ler de uma solicitação HTTP, eu escolheria json.Decoderuma vez que você obviamente está lendo de um fluxo.

James Henstridge
fonte
25
Além disso: inspecionando o código-fonte do Go 1.3, também podemos aprender que, para codificação, se você usar um json.Encoder, ele reutilizará um buffer pool global (suportado pelo novo sync.Pool), o que deve diminuir bastante a rotatividade do buffer. se você estiver codificando muito json. Existe apenas um pool global tão diferente do json.Encoder. A razão pela qual isso não pôde ser feito para a interface json.Marshal é porque os bytes são retornados ao usuário e o usuário não tem como "retornar" os bytes ao pool. Portanto, se você estiver fazendo muita codificação, o json.Marshal sempre apresenta uma grande rotatividade de buffer.
Aktau
@ Limlimzy: você tem certeza? O código fonte ainda diz que lê todo o valor no buffer antes de decodificar: github.com/golang/go/blob/master/src/encoding/json/… . O Bufferedmétodo existe para permitir que você veja quaisquer dados extras que foram lidos no buffer interno após o valor.
perfil completo de James Henstridge
@ JamesHenstridge: Não, você provavelmente está certo. Eu estava apenas interpretando sua afirmação de maneira diferente do que você pretendia. Desculpas pela confusão.
Flimzy