Struct tag adalah anotasi string di belakang deklarasi field yang dibaca package encoding saat runtime. Dengan tag, nama field JSON tidak lagi terikat nama field Go: NamaLengkap bisa tampil sebagai nama_lengkap, field kosong bisa dipangkas dari output, dan field rahasia bisa disembunyikan total. Pada chapter ini kita akan membedah anatomi struct tag, perilaku omitempty terhadap zero value, penulisan tag untuk beberapa format sekaligus, dan pembacaan tag lewat reflection.
Anatomi Struct Tag #
Struct tag ditulis sebagai string literal mentah di belakang tipe field:
Nama string `json:"nama" xml:"nama"`String tag terdiri atas pasangan kunci:"nilai" yang dipisah spasi. Setiap package encoding membaca kunci miliknya sendiri: encoding/json membaca json, encoding/xml membaca xml. Nilai di balik kunci lalu dipecah lagi dengan koma menjadi nama dan opsi, misalnya nama,omitempty. Tabel berikut merangkum atribut tag JSON yang paling sering dipakai:
| Atribut | Efek saat marshal | Efek saat unmarshal |
|---|---|---|
json:"nama" | Field tampil dengan nama kecil | Pencocokan nama, tidak peka huruf besar-kecil |
json:"nama,omitempty" | Field zero tidak ditulis | Sama seperti nama |
json:"-" | Field tidak ditulis | Field tidak diisi |
json:",omitempty" | Nama field Go dipertahankan | Nama field Go |
json:"nama,string" | Nilai ditulis sebagai string | String diurai menjadi nilai |
omitempty dan Zero Value #
omitempty memangkas field yang bernilai zero: string kosong, angka nol, false, slice dan map kosong, serta pointer nil. Perilaku ini membuat output JSON ramping, tetapi juga menghapus makna “benar-benar kosong” versus “tidak diisi”. Trik pointer mengembalikan perbedaan tersebut: pointer nil tetap dipangkas, sedangkan pointer ke nilai zero tetap ditulis. Program tag-demo.go berikut memperagakan seluruh perilaku tersebut:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Produk struct {
ID string `json:"id" xml:"id,attr"`
Nama string `json:"nama,omitempty" xml:"nama"`
Harga int `json:"harga,omitempty" xml:"-"`
Aktif bool `json:"aktif,omitempty" xml:"aktif"`
Tag []string `json:"tag,omitempty" xml:"tag"`
Stok *int `json:"stok,omitempty" xml:"-"`
Rahasia string `json:"-" xml:"-"`
Kategori string `json:"kategori,omitempty" xml:"kategori"`
}
func main() {
// Semua field opsional bernilai zero: omitempty memangkasnya.
p := Produk{ID: "P-01", Nama: "Buku Go", Harga: 95000, Aktif: true}
stok := 0 // pointer ke nol tetap ikut output
p.Stok = &stok
data, err := json.Marshal(p)
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
// Hanya ID terisi: field lain bernilai zero dan hilang.
data2, _ := json.Marshal(Produk{ID: "P-02"})
fmt.Println(string(data2))
// Decode: pencocokan nama field tidak peka huruf besar-kecil,
// field yang tidak dikenal (stok, berat, diskon) diabaikan.
sumber := []byte(`{"ID":"P-03","NAMA":"Kopi","harga":15000,"stok":7,"berat":250,"diskon":true}`)
var p3 Produk
if err := json.Unmarshal(sumber, &p3); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: ID=%s Nama=%s Harga=%d", p3.ID, p3.Nama, p3.Harga)
if p3.Stok != nil {
fmt.Printf(" stok=%d", *p3.Stok)
}
fmt.Println()
// reflect membaca struct tag saat runtime.
t := reflect.TypeOf(p)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("field %-8s json=%-18q xml=%q\n", f.Name, f.Tag.Get("json"), f.Tag.Get("xml"))
}
}Output yang Dihasilkan:
{"id":"P-01","nama":"Buku Go","harga":95000,"aktif":true,"stok":0}
{"id":"P-02"}
hasil decode: ID=P-03 Nama=Kopi Harga=15000 stok=7
field ID json="id" xml="id,attr"
field Nama json="nama,omitempty" xml="nama"
field Harga json="harga,omitempty" xml="-"
field Aktif json="aktif,omitempty" xml="aktif"
field Tag json="tag,omitempty" xml="tag"
field Stok json="stok,omitempty" xml="-"
field Rahasia json="-" xml="-"
field Kategori json="kategori,omitempty" xml="kategori"Mari kita telaah baris demi baris program di atas:
- Baris 9-18 (type Produk): setiap field memuat tag
jsonsekaligusxml— dua format dalam satu deklarasi. FieldRahasiabertag"-"pada kedua format sehingga tidak pernah keluar. - Baris 22-24: struct dengan banyak field terisi, tetapi
p.Stokdiarahkan ke angka nol lewat pointer —"stok":0tetap muncul pada output justru karena nilainya bukan pointernil. - Baris 25-30:
json.Marshalmenghasilkan JSON dengan nama field kecil sesuai tag;aktifmuncul karena bernilaitrue,tagdankategorihilang karena zero. - Baris 33-34: struct kedua hanya mengisi
ID, sehingga seluruh field bertagomitemptyterpangkas dan output tinggal{"id":"P-02"}. - Baris 38-48: decode memakai JSON dengan nama field campuran huruf besar dan kecil; pencocokan nama tidak peka huruf, dan field
beratsertadiskonyang tidak dikenal diabaikan tanpa galat. - Baris 51-55:
reflect.TypeOfmengambil tipe struct saat runtime;t.Field(i).Tag.Get("json")membaca nilai tag per field — mekanisme yang sama dipakai package encoding di balik layar.
Decode Tidak Peka Huruf Besar-Kecil #
Output kedua program membuktikan dua kelonggaran json.Unmarshal ketika memakai tag: nama field dicocokkan tanpa memperhatikan huruf besar-kecil, dan field JSON yang tidak dikenal diperbolehkan tanpa galat. Kelonggaran pertama menjaga kompatibilitas dengan sumber data yang berbeda gaya penamaan; kelonggaran kedua menjadi fondasi evolusi format yang dibahas pada chapter Format dan Compatibility
.
Satu Struct, Banyak Format #
Deklarasi xml:"id,attr" pada struct di atas menunjukkan bahwa satu field bisa melayani format berbeda sekaligus. Keluarga tag populer lain misalnya yaml:"nama" untuk YAML dan db:"nama" untuk pemetaan kolom basis data. Package YAML dan pemetaan database tersebut datang dari pihak ketiga, tetapi mekanisme pembacaannya sama: string tag diurai dengan reflect.StructTag.Get sehingga setiap library cukup memilih kunci sendiri.
Membaca Tag dengan reflect #
Bagian akhir program memakai reflect.TypeOf(p) untuk menelusuri field struct dan mencetak tag masing-masing. f.Tag.Get("json") mengembalikan nilai setelah kunci json, atau string kosong bila kunci tidak ada. Kebutuhan praktisnya bukan sekadar mencetak: validator, ORM ringan, dan generator kode memakai mekanisme ini untuk menemukan field dan aturannya tanpa menulis kode per struct.
Referensi #
- Dokumentasi package encoding/json — daftar lengkap atribut tag JSON.
- Dokumentasi package reflect
— aturan
StructTag.Get. - Blog Go: JSON and Go — penjelasan struct tag pada bagian field tagging.
Tag mengatur bentuk data yang keluar masuk, tetapi tidak memeriksa isinya. Apakah field wajib terisi, rentang umur masuk akal, dan format email benar adalah pekerjaan lapisan validasi — materi chapter Validasi Data berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
tag-demo.go
Unduh berkaspackage main
import (
"encoding/json"
"fmt"
"reflect"
)
type Produk struct {
ID string `json:"id" xml:"id,attr"`
Nama string `json:"nama,omitempty" xml:"nama"`
Harga int `json:"harga,omitempty" xml:"-"`
Aktif bool `json:"aktif,omitempty" xml:"aktif"`
Tag []string `json:"tag,omitempty" xml:"tag"`
Stok *int `json:"stok,omitempty" xml:"-"`
Rahasia string `json:"-" xml:"-"`
Kategori string `json:"kategori,omitempty" xml:"kategori"`
}
func main() {
// Semua field opsional bernilai zero: omitempty memangkasnya.
p := Produk{ID: "P-01", Nama: "Buku Go", Harga: 95000, Aktif: true}
stok := 0 // pointer ke nol tetap ikut output
p.Stok = &stok
data, err := json.Marshal(p)
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
// Hanya ID terisi: field lain bernilai zero dan hilang.
data2, _ := json.Marshal(Produk{ID: "P-02"})
fmt.Println(string(data2))
// Decode: pencocokan nama field tidak peka huruf besar-kecil,
// field yang tidak dikenal (stok, berat, diskon) diabaikan.
sumber := []byte(`{"ID":"P-03","NAMA":"Kopi","harga":15000,"stok":7,"berat":250,"diskon":true}`)
var p3 Produk
if err := json.Unmarshal(sumber, &p3); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: ID=%s Nama=%s Harga=%d", p3.ID, p3.Nama, p3.Harga)
if p3.Stok != nil {
fmt.Printf(" stok=%d", *p3.Stok)
}
fmt.Println()
// reflect membaca struct tag saat runtime.
t := reflect.TypeOf(p)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("field %-8s json=%-18q xml=%q\n", f.Name, f.Tag.Get("json"), f.Tag.Get("xml"))
}
}