Dekodieren von JSON mit json.Unmarshal vs json.NewDecoder.Decode

200

Ich entwickle einen API-Client, in dem ich auf Anfrage eine JSON-Nutzlast codieren und einen JSON-Body aus der Antwort decodieren muss.

Ich habe den Quellcode aus mehreren Bibliotheken gelesen und nach dem, was ich gesehen habe, habe ich grundsätzlich zwei Möglichkeiten, eine JSON-Zeichenfolge zu codieren und zu decodieren.

Verwenden Sie die json.UnmarshalÜbergabe der gesamten Antwortzeichenfolge

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

oder mit json.NewDecoder.Decode

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

In meinem Fall io.Readerscheint die zweite Version beim Umgang mit implementierten HTTP-Antworten weniger Code zu erfordern, aber da ich beide gesehen habe, frage ich mich, ob es eine Präferenz gibt, ob ich eine Lösung anstelle der anderen verwenden soll.

Darüber hinaus lautet die akzeptierte Antwort auf diese Frage

Bitte verwenden Sie json.Decoderanstelle von json.Unmarshal.

aber der Grund wurde nicht erwähnt. Sollte ich die Verwendung wirklich vermeiden json.Unmarshal?

Simone Carletti
quelle
Diese Pull-Anfrage auf GitHub ersetzte einen Aufruf von Unmarshal durch json.NewDecoder, um "Puffer bei der JSON-Decodierung zu entfernen".
Matt
Es hängt nur davon ab, welche Eingabe für Sie bequemer ist. blog.golang.org/json-and-go gibt Beispiele für die Verwendung beider Techniken.
rexposadas
15
IMO ioutil.ReadAllist fast immer das Falsche. Es hängt nicht mit Ihrem Ziel zusammen, erfordert jedoch, dass Sie über genügend zusammenhängenden Speicher verfügen, um alles zu speichern, was möglicherweise über die Pipe kommt, selbst wenn die letzten 20 TB der Antwort nach den letzten }in Ihrem JSON liegen.
Dustin
@Dustin Sie können dies verwenden io.LimitReader, um dies zu verhindern.
Inanc Gumus

Antworten:

238

Es hängt wirklich davon ab, was Ihre Eingabe ist. Wenn Sie sich die Implementierung der DecodeMethode von ansehen json.Decoder, wird der gesamte JSON-Wert im Speicher gepuffert, bevor die Zuordnung zu einem Go-Wert aufgehoben wird. In den meisten Fällen wird es also nicht speichereffizienter sein (obwohl sich dies in einer zukünftigen Version der Sprache leicht ändern könnte).

Eine bessere Faustregel lautet also:

  • Verwenden json.DecoderSie diese Option, wenn Ihre Daten aus einem io.ReaderDatenstrom stammen oder Sie mehrere Werte aus einem Datenstrom dekodieren müssen.
  • Verwenden json.UnmarshalSie diese Option, wenn Sie die JSON-Daten bereits im Speicher haben.

Für den Fall des Lesens aus einer HTTP-Anfrage würde ich auswählen, json.Decoderda Sie offensichtlich aus einem Stream lesen.

James Henstridge
quelle
25
Außerdem: Durch Überprüfen des Go 1.3-Quellcodes können wir auch feststellen, dass bei der Codierung bei Verwendung eines json.Encoder ein globaler Pufferpool (unterstützt durch den neuen sync.Pool) wiederverwendet wird, wodurch die Pufferabwanderung erheblich verringert werden sollte wenn Sie viel json codieren. Es gibt nur einen globalen Pool, der so unterschiedlich ist. Encoder teilt ihn. Der Grund, warum dies für die json.Marshal-Schnittstelle nicht möglich war, liegt darin, dass die Bytes an den Benutzer zurückgegeben werden und der Benutzer keine Möglichkeit hat, die Bytes an den Pool zurückzugeben. Wenn Sie also viel codieren, hat json.Marshal immer eine ziemliche Pufferabwanderung.
Aktau
@Flimzy: bist du sicher? Der Quellcode sagt immer noch, dass er den gesamten Wert vor dem Decodieren in den Puffer liest: github.com/golang/go/blob/master/src/encoding/json/… . Mit dieser BufferedMethode können Sie alle zusätzlichen Daten anzeigen, die nach dem Wert in den internen Puffer eingelesen wurden.
James Henstridge
@ JamesHenstridge: Nein, du hast wahrscheinlich recht. Ich habe Ihre Aussage nur anders interpretiert, als Sie beabsichtigt hatten. Entschuldigung für die Verwirrung.
Flimzy