JSON dan CSV mudah dibaca mata, tetapi teks tersebut boros ukuran dan lambat diurai ketika kedua ujung pertukaran data sama-sama program Go. Package encoding/gob menghadirkan format biner khusus Go: encoder mengirim nilai lengkap dengan deskripsi tipenya, decoder memeriksa kecocokan tipe, lalu menyalin nilai ke variabel tujuan. Pada chapter ini kita akan melakukan round-trip struct lewat bytes.Buffer, membuktikan field unexported tidak ikut, dan menangkap galat ketika tipe sumber serta tujuan tidak cocok.

Kapan gob, Kapan JSON #

gob bukan pengganti JSON. Format gob hanya dipahami program Go, sehingga tidak cocok untuk API publik. Kekuatan gob muncul pada kanal internal antar komponen Go: cache lokal di berkas atau Redis, session store, antrian pesan internal, hingga dasar package net/rpc. Tabel berikut merangkum perbedaan keduanya:

AspekJSONgob
Bentuk dataTeks, bisa dibaca mataBiner, tidak untuk dibaca
KonsumenBahasa apa punHanya program Go
Cocok tipeLonggar, berbasis nama fieldKetat, nama dan tipe field harus cocok
Kecepatan dan ukuranLebih lambat, lebih besarLebih cepat, lebih ringkas
Kasus pakaiAPI publik, konfigurasiCache, RPC, antrian internal

Round-Trip di Atas bytes.Buffer #

Alur gob selalu tiga aktor: satu io.Writer atau io.Reader, satu gob.NewEncoder/gob.NewDecoder di atasnya, dan nilai yang dikirim lewat Encode atau diterima lewat Decode. Program gob-demo.go berikut memakai bytes.Buffer sebagai perantara — pola yang sama berlaku untuk os.File, koneksi jaringan, atau bytes.Buffer di balik net/rpc:

go
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
)

type Pemesanan struct {
	ID     string
	Item   string
	Jumlah int
}

type Tagihan struct {
	ID     string
	Item   string
	Jumlah string // sengaja beda tipe dari Pemesanan.Jumlah
}

type Pengguna struct {
	Nama string
	nik  string // unexported: tidak pernah ikut ke gob
}

func main() {
	var buf bytes.Buffer
	if err := gob.NewEncoder(&buf).Encode(Pemesanan{ID: "P-001", Item: "Buku Go", Jumlah: 2}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	fmt.Printf("ukuran data biner: %d byte\n", buf.Len())

	var hasil Pemesanan
	if err := gob.NewDecoder(&buf).Decode(&hasil); err != nil {
		fmt.Println("decode gagal:", err)
		return
	}
	fmt.Printf("hasil decode: %+v\n", hasil)

	var bufP bytes.Buffer
	if err := gob.NewEncoder(&bufP).Encode(Pengguna{Nama: "Siti", nik: "3201010101010001"}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	var p2 Pengguna
	if err := gob.NewDecoder(&bufP).Decode(&p2); err != nil {
		fmt.Println("decode gagal:", err)
		return
	}
	fmt.Printf("pengguna: %+v\n", p2)

	var bufT bytes.Buffer
	if err := gob.NewEncoder(&bufT).Encode(Pemesanan{ID: "P-002", Item: "Kopi", Jumlah: 1}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	var t Tagihan
	err := gob.NewDecoder(&bufT).Decode(&t)
	fmt.Println("galat tipe beda:", err)
}

Output yang Dihasilkan:

text
ukuran data biner: 72 byte
hasil decode: {ID:P-001 Item:Buku Go Jumlah:2}
pengguna: {Nama:Siti nik:}
galat tipe beda: gob: wrong type (string) for received field Pemesanan.Jumlah

Mari kita telaah baris demi baris program di atas:

  1. Baris 9-13 (type Pemesanan): struct sumber dengan tiga field exported yang akan dikirim ke stream gob.
  2. Baris 15-19 (type Tagihan): struct tujuan yang sengaja membedakan tipe field Jumlah supaya galat tipe bisa dipancing.
  3. Baris 27-32: gob.NewEncoder(&buf).Encode(...) mengirim nilai Pemesanan ke buffer. Baris 32 mencetak ukuran hasil biner — kita sengaja tidak mencetak isi byte mentahnya karena bentuk biner tidak dimaksudkan untuk dibaca manusia.
  4. Baris 34-39: gob.NewDecoder(&buf).Decode(&hasil) membaca kembali isi buffer ke struct tujuan; Decode memerlukan pointer agar bisa mengisi variabel.
  5. Baris 41-51: percobaan kedua memakai struct Pengguna yang punya field nik unexported. Hasil decode menunjukkan nik kosong: gob hanya mengirim field exported, sama seperti JSON dan XML.
  6. Baris 53-60: percobaan ketiga mengirim Pemesanan lalu mendecode ke struct Tagihan yang field Jumlah-nya bertipe string. Karena nama field sama tetapi tipe berbeda, decoder menolak dengan galat gob: wrong type (string) for received field Pemesanan.Jumlah.

Ketatnya Pemeriksaan Tipe #

Galat pada percobaan ketiga adalah perilaku inti gob: decoder mencocokkan setiap field yang dikirim dengan field bernama sama pada tipe tujuan, dan tipe keduanya harus persis sama. Field yang hanya ada di satu sisi tidak menimbulkan galat — field sumber yang tak dikenal diabaikan, field tujuan yang kosong dibiarkan bernilai zero. Pemeriksaan ketat inilah yang membuat gob aman untuk pertukaran internal: perubahan tipe yang tidak disengaja langsung ketahuan saat decode, bukan diam-diam menghasilkan data salah.

Nilai Interface Perlu gob.Register #

Encode dan Decode bekerja otomatis untuk tipe konkret. Ketika yang dikirim adalah nilai interface, misalnya error atau field any, decoder tidak tahu tipe konkret yang disembunyikan di balik interface tersebut. Solusinya: panggil gob.Register(nilai konkret) sekali di awal program agar encoder boleh mengirim nama tipenya dan decoder tahu cara membangunnya kembali. Panggilan Register biasanya diletakkan di init() atau awal main().

Referensi #

gob memetakan struct apa adanya. Ketika kita ingin bentuk keluaran yang sepenuhnya dikendalikan — format tanggal tetap atau uang dalam satuan sen — JSON menyediakan pintu MarshalJSON yang kita bahas pada chapter Custom Marshaler berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

gob-demo.go

Unduh berkas
go
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
)

type Pemesanan struct {
	ID     string
	Item   string
	Jumlah int
}

type Tagihan struct {
	ID     string
	Item   string
	Jumlah string // sengaja beda tipe dari Pemesanan.Jumlah
}

type Pengguna struct {
	Nama string
	nik  string // unexported: tidak pernah ikut ke gob
}

func main() {
	var buf bytes.Buffer
	if err := gob.NewEncoder(&buf).Encode(Pemesanan{ID: "P-001", Item: "Buku Go", Jumlah: 2}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	fmt.Printf("ukuran data biner: %d byte\n", buf.Len())

	var hasil Pemesanan
	if err := gob.NewDecoder(&buf).Decode(&hasil); err != nil {
		fmt.Println("decode gagal:", err)
		return
	}
	fmt.Printf("hasil decode: %+v\n", hasil)

	var bufP bytes.Buffer
	if err := gob.NewEncoder(&bufP).Encode(Pengguna{Nama: "Siti", nik: "3201010101010001"}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	var p2 Pengguna
	if err := gob.NewDecoder(&bufP).Decode(&p2); err != nil {
		fmt.Println("decode gagal:", err)
		return
	}
	fmt.Printf("pengguna: %+v\n", p2)

	var bufT bytes.Buffer
	if err := gob.NewEncoder(&bufT).Encode(Pemesanan{ID: "P-002", Item: "Kopi", Jumlah: 1}); err != nil {
		fmt.Println("encode gagal:", err)
		return
	}
	var t Tagihan
	err := gob.NewDecoder(&bufT).Decode(&t)
	fmt.Println("galat tipe beda:", err)
}