Decoding JSON menggunakan json.Unmarshal vs json.NewDecoder.Decode

201

Saya sedang mengembangkan klien API di mana saya harus menyandikan muatan JSON berdasarkan permintaan dan mendekode badan JSON dari respons.

Saya telah membaca kode sumber dari beberapa perpustakaan dan dari apa yang telah saya lihat, pada dasarnya saya memiliki dua kemungkinan untuk menyandikan dan mendekode string JSON.

Gunakan json.Unmarshalmeneruskan seluruh string respons

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

atau menggunakan json.NewDecoder.Decode

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

Dalam kasus saya, ketika berhadapan dengan respons HTTP yang mengimplementasikan io.Reader, versi kedua tampaknya memerlukan lebih sedikit kode, tetapi karena saya telah melihat keduanya, saya bertanya-tanya apakah ada preferensi apakah saya harus menggunakan solusi daripada yang lain.

Selain itu, jawaban yang diterima dari pertanyaan ini mengatakan

Silakan gunakan json.Decodersebagai ganti json.Unmarshal.

tapi itu tidak menyebutkan alasannya. Haruskah saya benar-benar menghindari menggunakan json.Unmarshal?

Simone Carletti
sumber
Ini permintaan tarik pada GitHub diganti panggilan untuk unmarshal dengan json.NewDecoder untuk "menghapus penyangga di JSON decoding."
Matt
Itu tergantung pada input apa yang lebih nyaman untuk Anda gunakan. blog.golang.org/json-and-go memberikan contoh penggunaan kedua teknik.
rexposadas
15
IMO, ioutil.ReadAlladalah hampir selalu hal yang salah untuk dilakukan. Ini tidak terkait dengan tujuan Anda, tetapi mengharuskan Anda untuk memiliki cukup memori yang berdekatan untuk menyimpan apa pun yang mungkin terjadi, bahkan jika 20TB respons terakhir adalah setelah yang terakhir }di JSON Anda.
Dustin
@Dustin dapat Anda gunakan io.LimitReaderuntuk mencegahnya.
Inanc Gumus

Jawaban:

239

Itu benar-benar tergantung pada apa input Anda. Jika Anda melihat implementasi Decodemetode json.Decoder, itu buffer seluruh nilai JSON dalam memori sebelum mengosongkannya menjadi nilai Go. Jadi, dalam kebanyakan kasus, ini tidak akan lebih hemat memori (meskipun ini dapat dengan mudah berubah dalam versi bahasa yang akan datang).

Jadi aturan praktis yang lebih baik adalah ini:

  • Gunakan json.Decoderjika data Anda berasal dari io.Readeraliran, atau Anda perlu mendekodekan beberapa nilai dari aliran data.
  • Gunakan json.Unmarshaljika Anda sudah memiliki data JSON dalam memori.

Untuk kasus membaca dari permintaan HTTP, saya akan memilih json.Decoderkarena Anda jelas membaca dari aliran.

James Henstridge
sumber
25
Juga: dengan memeriksa kode sumber Go 1.3, kita juga dapat belajar bahwa untuk penyandian, jika Anda menggunakan json.Encoder, itu akan menggunakan kembali kumpulan buffer global (didukung oleh sinkronisasi baru. jika Anda menyandikan banyak json. Hanya ada satu kumpulan global yang sangat berbeda. Json. Berbagi dengan itu. Alasan ini tidak dapat dilakukan untuk antarmuka json.Marshal adalah karena byte dikembalikan ke pengguna dan pengguna tidak memiliki cara untuk "mengembalikan" byte ke kolam. Jadi jika Anda melakukan banyak penyandian, json.Marshal selalu memiliki churn penyangga yang cukup banyak.
Aktau
@Flimzy: Anda yakin? Kode sumber masih mengatakan itu membaca seluruh nilai ke dalam buffer sebelum decoding: github.com/golang/go/blob/master/src/encoding/json/… . The BufferedMetode yang ada untuk membiarkan Anda melihat data tambahan yang dibacakan ke dalam buffer internal setelah nilai.
James Henstridge
@ James Henstridge: Tidak, Anda mungkin benar. Saya hanya menafsirkan pernyataan Anda secara berbeda dari yang Anda maksudkan. Permintaan maaf untuk kebingungan.
Flimzy