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


203

Sto sviluppando un client API in cui ho bisogno di codificare un payload JSON su richiesta e decodificare un corpo JSON dalla risposta.

Ho letto il codice sorgente da diverse librerie e da quello che ho visto, ho sostanzialmente due possibilità per codificare e decodificare una stringa JSON.

Utilizzare il json.Unmarshalpassaggio dell'intera stringa di risposta

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

o usando json.NewDecoder.Decode

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

Nel mio caso, quando si tratta di risposte HTTP che implementano io.Reader, la seconda versione sembra richiedere meno codice, ma dal momento che ho visto entrambi mi chiedo se c'è qualche preferenza se dovrei usare una soluzione piuttosto che l'altra.

Inoltre, dice la risposta accettata da questa domanda

Si prega di utilizzare json.Decoderinvece di json.Unmarshal.

ma non ha menzionato il motivo. Dovrei davvero evitare di usare json.Unmarshal?


Questa richiesta pull su GitHub ha sostituito una chiamata a Unmarshal con json.NewDecoder per "rimuovere il buffer nella decodifica JSON".
Matt,

Dipende solo da quale input è più comodo da usare. blog.golang.org/json-and-go fornisce esempi sull'uso di entrambe le tecniche.
rexposadas,

15
IMO, ioutil.ReadAllè quasi sempre la cosa sbagliata da fare. Non è correlato al tuo obiettivo, ma richiede di disporre di memoria contigua sufficiente per archiviare tutto ciò che potrebbe venire giù dalla pipe, anche se gli ultimi 20 TB di risposta sono successivi all'ultimo }nel tuo JSON.
Dustin,

@Dustin Puoi usarlo io.LimitReaderper impedirlo.
Inanc Gumus

Risposte:


240

Dipende davvero da quale input hai. Se osservi l'implementazione del Decodemetodo di json.Decoder, memorizza in memoria l'intero valore JSON prima di smascherarlo in un valore Go. Quindi nella maggior parte dei casi non sarà più efficiente la memoria (anche se questo potrebbe facilmente cambiare in una versione futura della lingua).

Quindi una regola empirica migliore è questa:

  • Utilizzare json.Decoderse i dati provengono da un io.Readerflusso o è necessario decodificare più valori da un flusso di dati.
  • Utilizzare json.Unmarshalse si hanno già i dati JSON in memoria.

Per il caso di leggere da una richiesta HTTP, sceglierei json.Decoderpoiché stai ovviamente leggendo da uno stream.


25
Inoltre: ispezionando il codice sorgente Go 1.3, possiamo anche imparare che per la codifica, se si utilizza un json.Encoder, verrà riutilizzato un pool di buffer globale (supportato dal nuovo sync.Pool), che dovrebbe ridurre molto il churn del buffer se stai codificando un sacco di json. C'è solo un pool globale così diverso da json.Encoder lo condivide. Il motivo per cui non è stato possibile farlo per l'interfaccia json.Marshal è perché i byte vengono restituiti all'utente e l'utente non ha un modo per "restituire" i byte al pool. Quindi, se stai facendo molta codifica, json.Marshal ha sempre un po 'di buffer churn.
Aktau,

@Flimzy: sei sicuro? Il codice sorgente dice ancora che legge l'intero valore nel buffer prima della decodifica: github.com/golang/go/blob/master/src/encoding/json/… . Il Bufferedmetodo è lì per farti vedere tutti i dati extra che sono stati letti nel buffer interno dopo il valore.
James Henstridge,

@JamesHenstridge: No, probabilmente hai ragione. Stavo solo interpretando la tua affermazione in modo diverso da quello che volevi. Scuse per la confusione.
Flimzy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.