Hampir semua API web modern bertukar data dalam format JSON. Package encoding/json pada standard library Go menyediakan mesin dua arah untuk format tersebut: json.Marshal mengubah nilai Go menjadi teks JSON, sedangkan json.Unmarshal mengubah teks JSON kembali menjadi nilai Go. Pada chapter ini kita mempraktikkan kedua arah pada struct, slice, dan map, lalu menangkap galat decode dengan errors.As.
Marshal: Mengubah Nilai Go Menjadi JSON #
Fungsi json.Marshal menerima satu nilai bertipe any, membaca struktur nilai lewat reflection, lalu mengembalikan []byte berisi teks JSON. Ketika nilainya struct, json.Marshal hanya membaca field exported dan nama field output mengikuti nama field Go apa adanya:
data, err := json.Marshal(pengguna)Pemanggilan di atas menghasilkan JSON satu baris rapat, sedangkan json.MarshalIndent(pengguna, "", " ") menambahkan baris baru dan indentasi dua spasi. Tabel berikut merangkum pintu masuk utama package encoding/json:
| API | Arah | Kapan dipakai |
|---|---|---|
json.Marshal / json.MarshalIndent | Go → JSON | Data sudah utuh di memori |
json.Unmarshal | JSON → Go | Teks JSON sudah utuh di memori |
json.NewEncoder / json.NewDecoder | Dua arah, stream | Data besar atau datang bertahap |
json.RawMessage | Menunda decode | Bagian dokumen diurai belakangan |
Program json-demo.go berikut memperagakan MarshalIndent dan Unmarshal pada struct bersarang, slice, map, field unexported, decode dinamis, dan galat decode:
package main
import (
"encoding/json"
"errors"
"fmt"
)
type Alamat struct {
Kota string
KodePos string
}
type Pengguna struct {
Nama string
Umur int
Email string
Alamat Alamat
Hobi []string
Pekerjaan map[string]string
}
type Akun struct {
Nama string
sandi string // unexported: tidak pernah ikut ke JSON
}
func main() {
p := Pengguna{
Nama: "Budi",
Umur: 25,
Email: "[email protected]",
Alamat: Alamat{Kota: "Bandung", KodePos: "40115"},
Hobi: []string{"membaca", "bersepeda"},
Pekerjaan: map[string]string{
"perusahaan": "PT Maju Bersama",
"posisi": "Backend Engineer",
},
}
data, err := json.MarshalIndent(p, "", " ")
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
sumber := []byte(`{
"Nama": "Siti",
"Umur": 28,
"Email": "[email protected]",
"Alamat": {"Kota": "Surabaya", "KodePos": "60111"},
"Hobi": ["memasak", "menjahit"],
"Pekerjaan": {"posisi": "Frontend Engineer"},
"Telepon": "0812-0000-0000"
}`)
var p2 Pengguna
if err := json.Unmarshal(sumber, &p2); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: %+v\n", p2)
dataAkun, _ := json.Marshal(Akun{Nama: "Siti", sandi: "rahasia-123"})
fmt.Println("akun:", string(dataAkun))
var dinamis map[string]any
json.Unmarshal([]byte(`{"umur": 25, "aktif": true, "nama": "Budi"}`), &dinamis)
for _, k := range []string{"nama", "umur", "aktif"} {
fmt.Printf("dinamis[%s] = %v bertipe %T\n", k, dinamis[k], dinamis[k])
}
var galatTipe Pengguna
err1 := json.Unmarshal([]byte(`{"Umur":"dua puluh lima"}`), &galatTipe)
fmt.Println("galat 1:", err1)
var targetTipe *json.UnmarshalTypeError
fmt.Println("galat 1 adalah UnmarshalTypeError?", errors.As(err1, &targetTipe))
var galatSintaks Pengguna
err2 := json.Unmarshal([]byte(`{"Nama":`), &galatSintaks)
fmt.Println("galat 2:", err2)
var targetSintaks *json.SyntaxError
fmt.Println("galat 2 adalah SyntaxError?", errors.As(err2, &targetSintaks))
}Output yang Dihasilkan:
{
"Nama": "Budi",
"Umur": 25,
"Email": "[email protected]",
"Alamat": {
"Kota": "Bandung",
"KodePos": "40115"
},
"Hobi": [
"membaca",
"bersepeda"
],
"Pekerjaan": {
"perusahaan": "PT Maju Bersama",
"posisi": "Backend Engineer"
}
}
hasil decode: {Nama:Siti Umur:28 Email:[email protected] Alamat:{Kota:Surabaya KodePos:60111} Hobi:[memasak menjahit] Pekerjaan:map[posisi:Frontend Engineer]}
akun: {"Nama":"Siti"}
dinamis[nama] = Budi bertipe string
dinamis[umur] = 25 bertipe float64
dinamis[aktif] = true bertipe bool
galat 1: json: cannot unmarshal string into Go struct field Pengguna.Umur of type int
galat 1 adalah UnmarshalTypeError? true
galat 2: unexpected end of JSON input
galat 2 adalah SyntaxError? trueMari kita telaah baris demi baris program di atas:
- Baris 9-21 (type Alamat dan Pengguna): dua struct bersarang;
Hobimenjadi array JSON danPekerjaanmenjadi objek JSON berkunci terurut. - Baris 23-26 (type Akun): struct uji dengan field
sandiunexported. - Baris 41-46:
json.MarshalIndentmengubahpmenjadi JSON berindentasi lalu mencetaknya sebagai teks. - Baris 48-56 (sumber): teks JSON masukan yang memuat field
Telepon, field yang tidak ada pada structPengguna. - Baris 58-63:
json.Unmarshal(sumber, &p2)mengisi struct lewat pointer; fieldTelepondiabaikan tanpa galat. - Baris 65-66: memarshal
Akun; output hanya memuat{"Nama":"Siti"}karenasanditidak dibaca. - Baris 68-72: decode JSON ke
map[string]any; verb%Tmencetak tipe tiap nilai. - Baris 74-84: dua galat dipancing lalu diperiksa memakai
errors.Asdengan target*json.UnmarshalTypeErrordan*json.SyntaxError.
Field Unexported Hilang Diam-diam #
Output akun: {"Nama":"Siti"} membuktikan field sandi lenyap tanpa galat. Package encoding/json hanya mengakses field lewat reflection untuk identifier exported, sehingga field berhuruf kecil di awal nama tidak pernah dibaca maupun ditulis. Perilaku sama berlaku pada gob dan XML.
Decode Dinamis: any dan Jebakan float64 #
Ketika bentuk JSON tidak diketahui di muka, kita bisa mendecode ke map[string]any. Baris output dinamis[umur] = 25 bertipe float64 memperlihatkan jebakan klasik: setiap angka JSON menjadi float64, bukan int.
Galat Decode dan Jenisnya #
Galat json: cannot unmarshal string into Go struct field Pengguna.Umur of type int bertipe *json.UnmarshalTypeError: nilai JSON tidak cocok dengan tipe field tujuan. Galat unexpected end of JSON input bertipe *json.SyntaxError: teks JSON rusak sintaksnya. Dengan errors.As kita bisa membedakan masalah muatan dari masalah transport, sejalan dengan materi errors.Is dan errors.As
.
Stream: Decoder, Encoder, dan RawMessage #
json.Unmarshal membutuhkan seluruh teks JSON sekaligus di memori. Untuk data besar, json.NewDecoder membaca objek demi objek dari io.Reader, json.NewEncoder menulis objek demi objek ke io.Writer, dan json.RawMessage menunda decode bagian yang belum perlu diurai. Program json-stream-demo.go memperagakan ketiganya:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
)
type Barang struct {
ID string
Nama string
Stok int
}
func main() {
sumber := `{"ID":"B-01","Nama":"Kopi","Stok":12}
{"ID":"B-02","Nama":"Teh","Stok":8}
{"ID":"B-03","Nama":"Gula","Stok":5}`
dec := json.NewDecoder(strings.NewReader(sumber))
for {
var b Barang
err := dec.Decode(&b)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("decode gagal:", err)
return
}
fmt.Printf("baca stream: %+v\n", b)
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetIndent("", " ")
if err := enc.Encode(Barang{ID: "B-01", Nama: "Kopi", Stok: 12}); err != nil {
fmt.Println("encode gagal:", err)
return
}
if err := enc.Encode(Barang{ID: "B-02", Nama: "Teh", Stok: 8}); err != nil {
fmt.Println("encode gagal:", err)
return
}
fmt.Print(buf.String())
dokumen := `{"id":"T-9","total":3,"detail":{"panjang":14,"lebar":6}}`
var mentah map[string]json.RawMessage
if err := json.Unmarshal([]byte(dokumen), &mentah); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
var id string
json.Unmarshal(mentah["id"], &id)
fmt.Println("id:", id)
fmt.Printf("detail mentah: %s\n", mentah["detail"])
}Output yang Dihasilkan:
baca stream: {ID:B-01 Nama:Kopi Stok:12}
baca stream: {ID:B-02 Nama:Teh Stok:8}
baca stream: {ID:B-03 Nama:Gula Stok:5}
{
"ID": "B-01",
"Nama": "Kopi",
"Stok": 12
}
{
"ID": "B-02",
"Nama": "Teh",
"Stok": 8
}
id: T-9
detail mentah: {"panjang":14,"lebar":6}Mari kita telaah baris demi baris program di atas:
- Baris 18-20 (sumber): tiga objek JSON berjejer dalam satu teks;
json.Unmarshaltidak bisa memrosesnya sekaligus. - Baris 22-34:
json.NewDecodermembungkusstrings.NewReader; loopdec.Decode(&b)berjalan sampaiio.EOF. - Baris 36-38:
json.NewEncoderdibuat di atasbytes.Buffer;enc.SetIndentmengatur indentasi. - Baris 39-47: dua pemanggilan
enc.Encodemenulis dua objek JSON terpisah. - Baris 49-58: dokumen didecode ke
map[string]json.RawMessage;iddiurai belakangan,detailtetap mentah.
Referensi #
- Dokumentasi package encoding/json — referensi resmi API JSON.
- Dokumentasi json.Marshal — aturan pemetaan tipe.
- Dokumentasi json.Unmarshal — aturan decode dan galat.
- Blog Go: JSON and Go — pengantar resmi berikut contoh stream.
Setelah menguasai JSON, kita berpindah ke format lama yang masih dipakai luas di sistem enterprise: XML pada chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
json-demo.go
Unduh berkaspackage main
import (
"encoding/json"
"errors"
"fmt"
)
type Alamat struct {
Kota string
KodePos string
}
type Pengguna struct {
Nama string
Umur int
Email string
Alamat Alamat
Hobi []string
Pekerjaan map[string]string
}
type Akun struct {
Nama string
sandi string // unexported: tidak pernah ikut ke JSON
}
func main() {
p := Pengguna{
Nama: "Budi",
Umur: 25,
Email: "[email protected]",
Alamat: Alamat{Kota: "Bandung", KodePos: "40115"},
Hobi: []string{"membaca", "bersepeda"},
Pekerjaan: map[string]string{
"perusahaan": "PT Maju Bersama",
"posisi": "Backend Engineer",
},
}
data, err := json.MarshalIndent(p, "", " ")
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
sumber := []byte(`{
"Nama": "Siti",
"Umur": 28,
"Email": "[email protected]",
"Alamat": {"Kota": "Surabaya", "KodePos": "60111"},
"Hobi": ["memasak", "menjahit"],
"Pekerjaan": {"posisi": "Frontend Engineer"},
"Telepon": "0812-0000-0000"
}`)
var p2 Pengguna
if err := json.Unmarshal(sumber, &p2); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: %+v\n", p2)
dataAkun, _ := json.Marshal(Akun{Nama: "Siti", sandi: "rahasia-123"})
fmt.Println("akun:", string(dataAkun))
var dinamis map[string]any
json.Unmarshal([]byte(`{"umur": 25, "aktif": true, "nama": "Budi"}`), &dinamis)
for _, k := range []string{"nama", "umur", "aktif"} {
fmt.Printf("dinamis[%s] = %v bertipe %T\n", k, dinamis[k], dinamis[k])
}
var galatTipe Pengguna
err1 := json.Unmarshal([]byte(`{"Umur":"dua puluh lima"}`), &galatTipe)
fmt.Println("galat 1:", err1)
var targetTipe *json.UnmarshalTypeError
fmt.Println("galat 1 adalah UnmarshalTypeError?", errors.As(err1, &targetTipe))
var galatSintaks Pengguna
err2 := json.Unmarshal([]byte(`{"Nama":`), &galatSintaks)
fmt.Println("galat 2:", err2)
var targetSintaks *json.SyntaxError
fmt.Println("galat 2 adalah SyntaxError?", errors.As(err2, &targetSintaks))
}
json-stream-demo.go
Unduh berkaspackage main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
)
type Barang struct {
ID string
Nama string
Stok int
}
func main() {
sumber := `{"ID":"B-01","Nama":"Kopi","Stok":12}
{"ID":"B-02","Nama":"Teh","Stok":8}
{"ID":"B-03","Nama":"Gula","Stok":5}`
dec := json.NewDecoder(strings.NewReader(sumber))
for {
var b Barang
err := dec.Decode(&b)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("decode gagal:", err)
return
}
fmt.Printf("baca stream: %+v\n", b)
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetIndent("", " ")
if err := enc.Encode(Barang{ID: "B-01", Nama: "Kopi", Stok: 12}); err != nil {
fmt.Println("encode gagal:", err)
return
}
if err := enc.Encode(Barang{ID: "B-02", Nama: "Teh", Stok: 8}); err != nil {
fmt.Println("encode gagal:", err)
return
}
fmt.Print(buf.String())
dokumen := `{"id":"T-9","total":3,"detail":{"panjang":14,"lebar":6}}`
var mentah map[string]json.RawMessage
if err := json.Unmarshal([]byte(dokumen), &mentah); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
var id string
json.Unmarshal(mentah["id"], &id)
fmt.Println("id:", id)
fmt.Printf("detail mentah: %s\n", mentah["detail"])
}