Pendahuluan
JSON (JavaScript Object Notation) adalah format pertukaran data sederhana. Secara sintaks ia menyerupai objek dan list dari JavaScript. Ia umumnya digunakan untuk komunikasi antara web back-end dan program JavaScript yang berjalan di peramban, namun ia digunakan diberbagai tempat lainnya juga. Situsnya, json.org, menyediakan definisi standar yang jelas dan ringkas.
Paket json menyediakan cara yang cepat untuk membaca dan menulis data JSON dalam program Go anda.
Penulisan (encoding)
Untuk meng-encode data JSON kita menggunakan fungsi Marshal.
func Marshal(v interface{}) ([]byte, error)
Diberikan struktur data Go, Message
,
type Message struct { Name string Body string Time int64 }
dan sebuah instansi dari Message
m := Message{"Alice", "Hello", 1294706395881547000}
kita dapat meng-encode m
menjadi JSON menggunakan json.Marshal
:
b, err := json.Marshal(m)
Jika semua berjalan dengan baik, err
akan bernilai nil
dan b
akan berisi
[]byte
dari data JSON:
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
Hanya struktur data yang dapat direpresentasikan sebagai valid JSON yang akan di-encode:
-
JSON objek hanya mendukung string sebagai key; untuk meng-encode tipe Go map maka haruslah dalam bentuk
map[string]T
(yang manaT
ialah tipe Go apapun yang didukung oleh paket json). -
Tipe channel, complex, dan fungsi tidak dapat di-encode.
-
Struktur data berulang tidak didukung; ia akan menyebabkan
Marshal
menjadi pengulangan tanpa henti. -
Pointer akan di-encode menjadi nilai yang ditunjuknya (atau 'null’ jika pointer adalah
nil
).
Paket json hanya mengakses field-field yang diekspor pada tipe struct (yang berawalan dengan huruf besar). Oleh karena itu hanya field yang diekspor dari sebuah struct yang akan muncul dalam keluaran JSON.
Pembacaan (decoding)
Untuk membaca data JSON kita menggunakan fungsi Unmarshal.
func Unmarshal(data []byte, v interface{}) error
Pertama, kita harus membuat tempat yang menampung data yang akan dibaca,
var m Message
dan memanggil json.Unmarshal
, mengirim []byte
sebagai data JSON dan
pointer ke m
err := json.Unmarshal(b, &m)
Jika b
berisi valid JSON yang sesuai dengan m
, setelah pemanggilan fungsi
tersebut err
akan berisi nil
dan data dari b
akan disimpan dalam struct
m
, seperti melakukan penempatan:
m = Message{ Name: "Alice", Body: "Hello", Time: 1294706395881547000, }
Bagaimana fungsi Unmarshal
mengetahui field tempat menyimpan data yang
dibaca?
Untuk JSON dengan key "Foo", Unmarshal
akan mencari field dalam struct
tujuan (dengan urutan berikut):
-
Field yang diekspor dengan tag "Foo" (lihat spesifikasi Go untuk informasi lebih lanjut tentang tag pada struct),
-
Field yang diekspor bernama "Foo", atau
-
Field yang diekspor bernama "FOO" atau "FoO" yang mengacuhkan huruf besar-kecil yang sama dengan "Foo".
Apa yang terjadi bila struktur dari data JSON tidak mirip dengan tipe Go?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`) var m Message err := json.Unmarshal(b, &m)
Unmarshal
hanya akan membaca field yang ia temukan pada tipe tujuan.
Dalam kasus ini hanya field Name
dari m
yang akan diisi, dan field Food
akan diindahkan.
Perilaku ini berguna bila anda ingin mengambil hanya field-field tertentu dari
blob JSON yang besar.
Hal ini juga berarti bahwa field yang tidak diekspor pada struct tujuan tidak
akan dipengaruhi oleh Unmarshal
.
Namun bagaimana jika anda tidak mengetahui struktur dari data JSON sebelumnya?
JSON generik dengan interface{}
Tipe interface{}
(interface kosong) mendeskripsikan sebuah interface dengan
method yang kosong.
Setiap tipe pada Go mengimplementasikan paling tidak method kosong dan oleh
karena itu memenuhi interface kosong.
Interface kosong berfungsi sebagai penampung umum dari tipe:
var i interface{} i = "a string" i = 2011 i = 2.777
Sebuah asersi tipe mengakses tipe konkret didalamnya:
r := i.(float64) fmt.Println("area dari lingkaran yaitu", math.Pi*r*r)
Atau, jika tipe didalam interface kosong tersebut tidak diketahui, sebuah switch bertipe dapat menentukan tipenya:
switch v := i.(type) { case int: fmt.Println("nilai integer dari i adalah", v) case float64: fmt.Println("nilai float64 dari i adalah", v) case string: fmt.Println("nilai string dari i adalah", v) default: // i bukanlah salah satu dari tipe diatas. }
Paket json menggunakan nilai map[string]interface{}
dan []interface{}
untuk menyimpan objek dan array dari JSON;
ia akan membaca JSON blob yang valid menjadi nilai interface{}
.
Tipe konkret bawaan dari Go yaitu:
-
bool
untuk boolean JSON, -
float64
untuk angka JSON, -
string
untuk string JSON, dan -
nil
untuk null JSON.
Membaca data beragam
Misalkan data JSON berikut, disimpan dalam variabel b
:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
Tanpa mengetahui struktur datanya, kita dapat membacanya menjadi sebuah nilai
interface{}
dengan Unmarshal
:
var f interface{} err := json.Unmarshal(b, &f)
Nilai dalam f
yaitu sebuah map dengan key bertipe string dan nilai
disimpan dalam interface kosong:
f = map[string]interface{}{ "Name": "Wednesday", "Age": 6, "Parents": []interface{}{ "Gomez", "Morticia", }, }
Untuk mengakses data ini kita dapat menggunakan asersi tipe untuk mengakses
map[string]interface{}
di dalam f
:
m := f.(map[string]interface{})
Kita dapat melakukan iterasi pada map dengan perintah range
dan menggunakan
sebuah switch bertipe untuk mengakses nilai konkretnya:
for k, v := range m { switch vv := v.(type) { case string: fmt.Println(k, "adalah string", vv) case float64: fmt.Println(k, "adalah float64", vv) case []interface{}: fmt.Println(k, "adalah array:") for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, "adalah tipe yang tidak diketahui cara menanganinya") } }
Dengan cara ini kita dapat bekerja dengan data JSON yang tidak diketahui sebelumnya dengan masih diuntungkan dari keamanan tipe.
Tipe Referensi
Mari kita definisikan sebuah tipe Go yang berisi data dari contoh sebelumnya,
type FamilyMember struct { Name string Age int Parents []string } var m FamilyMember err := json.Unmarshal(b, &m)
Memanggil Unmarshal
pada data b
ke nilai dari FamilyMember
bekerja
seperti yang diharapkan, namun jika kita pelajari lebih dekat kita dapat
melihat hal yang menarik terjadi.
Dengan perintah var
kita mengalokasikan struct FamilyMember
, dan mengirim
pointer dari nilai tersebut ke fungsi Unmarshal
, namun pada saat tersebut
field Parents
memiliki nilai slice nil
.
Untuk mengisi field Parents
, Unmarshal
mengalokasikan slice baru secara
otomatis.
Dengan cara inilah Unmarshal
bekerja dengan tipe referensi (pointer, slice,
dan map).
Misalkan kita melakukan pembacaan ke dalam struktur data berikut:
type Foo struct { Bar *Bar }
Jika JSON objek memiliki field "Bar", Unmarshal
akan mengalokasikan sebuah
instansi dari Bar
yang baru dan mengisinya.
Jika tidak, Bar
akan diindahkan dan berisi pointer nil
.
Pola seperti ini berguna: jika anda memiliki aplikasi yang menerima beberapa tipe pesan yang berbeda, anda bisa mendefinisikan struktur "penerima" seperti berikut
type IncomingMessage struct { Cmd *Command Msg *Message }
dan pada bagian pengirim dapat mengisi field Cmd
dan/atau field Msg
dari
objek JSON, bergantung dari tipe pesan yang ingin dikomunikasikan.
Unmarshal
, saat membaca JSON ke struct IncomingMessage
, hanya akan
mengalokasikan struktur data yang ada dalam data JSON.
Untuk mengetahui pesan yang diproses, pemrogram perlu memeriksa apakah Cmd
atau Msg
yang bernilai nil
.
Menulis dan Membaca secara berkelanjutan (Streaming)
Paket json menyediakan tipe Decoder
dan Encoder
untuk mendukung operasi
pembacaan dan penulisan data JSON berkelanjutan (streaming).
Fungsi NewDecoder
dan NewEncoder
membungkus tipe interface
io.Reader
dan
io.Writer.
func NewDecoder(r io.Reader) *Decoder func NewEncoder(w io.Writer) *Encoder
Berikut contoh program yang membaca sekumpulan objek JSON dari standar input,
menghapus semua field kecuali Name
dari setiap objek, dan menulis objek ke
standar keluaran:
package main import ( "encoding/json" "log" "os" ) func main() { dec := json.NewDecoder(os.Stdin) enc := json.NewEncoder(os.Stdout) for { var v map[string]interface{} if err := dec.Decode(&v); err != nil { log.Println(err) return } for k := range v { if k != "Name" { delete(v, k) } } if err := enc.Encode(&v); err != nil { log.Println(err) } } }
Karena Reader dan Writer ada dimana-mana, tipe Encoder
dan Decoder
ini
dapat digunakan dalam rentang skenario yang luas, seperti membaca dan menulis
ke koneksi HTTP, WebSocket, atau berkas.
Referensi
Untuk informasi lebih lanjut lihat dokumentasi paket json. Untuk contoh penggunaan json lihat sumber berkas dari paket jsonrpc.