Tag JSON

Marshaling struct dengan encoding/json.

Daftar isi

Struct sering menjadi jembatan antara data di dalam program dan data dalam format JSON (JavaScript Object Notation), format pertukaran data yang lazim dipakai layanan API. Topik ini membahas marshal, yaitu proses mengubah struct menjadi JSON, beserta tag, yaitu anotasi pada deklarasi field yang mengatur nama kunci dan perilaku prosesnya. Setelah membaca topik ini, bentuk JSON sebuah struct dapat dikendalikan penuh, termasuk pengabaian field kosong dan proses kebalikannya.

Marshal struct menjadi JSON#

Fungsi json.Marshal dari package encoding/json menerima sebuah nilai dan mengembalikan JSON-nya dalam bentuk []byte bersama error. Tanpa tag apa pun, nama field dipakai sebagai nama kunci JSON. Program berikut menambahkan tag pada setiap field, sehingga kuncinya menjadi huruf kecil:

go kartu-json.go
package main

import (
	"encoding/json"
	"fmt"
)

// KartuAnggota menyimpan data kartu anggota perpustakaan.
type KartuAnggota struct {
	Nama     string `json:"nama"`
	Nomor    string `json:"nomor"`
	Pinjaman int    `json:"pinjaman"`
}

func main() {
	kartu := KartuAnggota{Nama: "Dewi", Nomor: "P-2024-115", Pinjaman: 2}

	data, err := json.Marshal(kartu)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(data))

	rapi, err := json.MarshalIndent(kartu, "", "  ")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(rapi))
}
text Keluaran
{"nama":"Dewi","nomor":"P-2024-115","pinjaman":2}
{
  "nama": "Dewi",
  "nomor": "P-2024-115",
  "pinjaman": 2
}

Keluaran pertama berupa satu baris JSON yang padat, sedangkan json.MarshalIndent menghasilkan JSON dengan lekukan (indent) agar mudah dibaca. Urutan kunci mengikuti urutan deklarasi field pada struct, sehingga keluaran untuk struct yang sama selalu identik. Sifat ini penting ketika keluaran JSON dibandingkan atau diuji. Pemanggilan string(data) mengubah []byte menjadi string karena tipe []byte tidak mencetak sebagai teks JSON.

Struct yang menyimpan struct lain dimarshal sebagai objek bersarang, dan field pada struct dalam mengikuti tag masing-masing. Susunan ini membuat struktur JSON mencerminkan struktur data di dalam program, sehingga objek yang kompleks tetap dapat diteruskan tanpa tipe perantara tambahan. Apabila bentuk JSON yang diminta berbeda jauh dari bentuk struct yang ada, struct perantara dengan tag yang sesuai tetap menjadi cara yang dianjurkan.

Mengatur kunci dengan tag#

Tag ditulis sebagai teks yang diapit tanda kutip di belakang deklarasi field, dan isi utamanya adalah nama kunci JSON yang diinginkan. Opsi tambahan menyusul setelah koma, dan kombinasi yang paling sering dipakai dirangkum pada tabel berikut.

TagKunci JSONPerilaku
tanpa tagNamaNama field dipakai apa adanya
json:"nama"namaKunci berganti mengikuti tag
json:"pinjaman,omitempty"pinjamanField dilewati bila nilainya zero value
json:"-"tidak adaField dilewati seluruhnya oleh proses marshal

Opsi omitempty menghilangkan field yang nilainya masih zero value, yaitu 0 untuk bilangan, "" untuk string, false untuk bool, nil untuk pointer dan interface, serta array, slice, dan map yang kosong. Program berikut memakai opsi tersebut, lalu menunjukkan proses kebalikannya, yaitu unmarshal:

go kartu-unmarshal.go
package main

import (
	"encoding/json"
	"fmt"
)

// KartuAnggota menyimpan data kartu anggota perpustakaan.
type KartuAnggota struct {
	Nama     string `json:"nama"`
	Pinjaman int    `json:"pinjaman,omitempty"`
	Catatan  string `json:"catatan,omitempty"`
}

func main() {
	kartu := KartuAnggota{Nama: "Ali"}

	data, err := json.Marshal(kartu)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("marshal   :", string(data))

	teks := []byte(`{"nama":"Budi","pinjaman":3}`)
	var hasil KartuAnggota
	if err := json.Unmarshal(teks, &hasil); err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("unmarshal : %+v\n", hasil)
}
text Keluaran
marshal   : {"nama":"Ali"}
unmarshal : {Nama:Budi Pinjaman:3 Catatan:}

Baris pertama keluaran menunjukkan bahwa Pinjaman bernilai 0 dan Catatan berupa string kosong sama sekali tidak muncul di JSON, sementara baris kedua membuktikan arah sebaliknya. Fungsi json.Unmarshal menerima JSON beserta pointer ke struct tujuan, sehingga hasilnya diisi pada struct yang asli. Kunci yang ada pada JSON tetapi tidak ada pada struct diabaikan tanpa error, sedangkan kunci yang hilang membuat field tujuan tetap pada zero value-nya.

Tag json:"-" bersifat dua arah, yaitu fieldnya dilewati saat marshal dan tidak diisi saat unmarshal. Opsi ini dipakai untuk data yang sengaja tidak boleh keluar dari program, misalnya penanda internal, sementara omitempty lebih cocok untuk data opsional yang tidak perlu dikirim bila kosong. Perbedaan keduanya perlu dijaga, karena yang satu menghilangkan kunci sepenuhnya dan yang lain menghilangkannya hanya pada kondisi tertentu.

Ketika unmarshal menemui ketidakcocokan#

Unmarshal menuntut kesesuaian tipe antara nilai JSON dan field tujuannya, dan ketidakcocokan dilaporkan sebagai error. JSON berupa string "dua" untuk field bertipe int menghasilkan pesan json: cannot unmarshal string into Go struct field KartuAnggota.pinjaman of type int, dan pesan tersebut menyebut nama field bermasalah beserta tipe tujuannya. Error semacam ini terjadi saat eksekusi, bukan saat kompilasi, karena isi JSON baru diketahui ketika program berjalan.

Kesalahan umum#

  • Melupakan tanda & pada argumen json.Unmarshal. Fungsi menuntut pointer agar struct tujuannya dapat diisi, dan tanpa tanda itu prosesnya gagal dengan pesan json: Unmarshal(non-pointer main.KartuAnggota). Perbaikannya, teruskan &hasil sebagaimana contoh program di atas.
  • Mengharapkan field tidak terekspor ikut dimarshal. Penyebabnya adalah lupa aturan ekspor Go, dan akibatnya data hilang dari JSON secara diam-diam. Perbaikannya, ekspor fieldnya, lalu pakai tag untuk mengganti nama kuncinya bila bentuk JSON tidak boleh berubah.
  • Membandingkan keluaran JSON dua struct yang fieldnya sama tetapi urutan deklarasinya berbeda. Kunci JSON mengikuti urutan deklarasi field, sehingga kedua keluaran tidak akan sama. Perbaikannya, samakan urutan deklarasi field pada tipe yang dibandingkan, atau bandingkan hasilnya setelah di-unmarshal ke tipe yang sama.
  • Mengira tag mengubah nama field di dalam kode Go. Tag hanya berlaku pada batas JSON, sehingga akses tetap menulis kartu.Nama meskipun kuncinya "nama". Perbaikannya, ingat bahwa tag menerjemahkan nama pada batas JSON saja, sedangkan kode Go selalu memakai nama field aslinya.

Ringkasan#

  • json.Marshal mengubah struct menjadi JSON, dengan nama field sebagai kunci bawaan dan tag sebagai pengganti nama kunci.
  • Opsi omitempty melewatkan field yang masih berupa zero value, sedangkan tag json:"-" melewatkan field sepenuhnya.
  • json.Unmarshal mengisi struct tujuan melalui pointer, mengabaikan kunci yang tidak dikenal, dan mempertahankan zero value untuk kunci yang hilang.
  • Urutan kunci JSON mengikuti urutan deklarasi field, sehingga keluaran marshal bersifat deterministik.

Referensi#