XML mendahului JSON dan sampai sekarang masih menggerakkan sistem lama: berkas konfigurasi, sitemap, feed, dokumen kantor, sampai protokol web service era 2000-an seperti SOAP. Package encoding/xml pada standard library Go memetakan struct ke elemen XML lewat struct tag, dengan mekanisme dua arah yang mirip encoding/json namun aturan penamaannya berbeda. Pada chapter ini kita akan menulis XML dari struct, membaca XML kembali ke struct, dan menguasai tiga bentuk struct tag XML yang paling sering dipakai: atribut, elemen anak, dan chardata.
Perbedaan Mendasah dengan JSON #
JSON hanya mengenal objek, array, dan nilai skalar, sedangkan XML mengenal elemen bersarang, atribut pada elemen, dan teks langsung di dalam elemen. Struct tag XML karena itu punya lebih banyak peran daripada tag JSON. Tabel berikut merangkum bentuk tag yang dipakai di chapter ini:
| Bentuk tag | Peran field | Contoh hasil |
|---|---|---|
xml:"nama" | Elemen anak | <nama>Budi</nama> |
xml:"nama,attr" | Atribut elemen induk | <buku nama="Budi"> |
xml:",chardata" | Teks di dalam elemen itu sendiri | <harga>95000</harga> |
xml:"-" | Field tidak ikut XML | (tidak muncul) |
Sejarah singkat: XML lahir sebagai format dokumen serbaguna pada akhir 1990-an, lalu menjadi tulang punggung web service berbasis SOAP dan WSDL sebelum JSON mengambil alih karena lebih ringan. Kode Go masa kini masih bertemu XML ketika berintegrasi dengan sistem perbankan, pemerintahan, atau layanan lawas yang tidak bisa diubah.
Menulis XML dari Struct #
Nama elemen akar diambil dari nama struct, kecuali field XMLName xml.Name menentukan nama lain. xml.MarshalIndent(k, "", " ") menghasilkan XML berindentasi, kebalikan dari xml.Marshal yang menghasilkan satu baris rapat. Program xml-demo.go berikut memperagakan penulisan sekaligus pembacaan XML:
package main
import (
"encoding/xml"
"fmt"
)
type Harga struct {
MataUang string `xml:"mata_uang,attr"`
Nilai string `xml:",chardata"`
}
type Buku struct {
ID string `xml:"id,attr"`
Judul string `xml:"judul"`
Penulis string `xml:"penulis"`
Harga Harga `xml:"harga"`
Rating int `xml:"-"` // tidak pernah ikut ke XML
}
type Katalog struct {
XMLName xml.Name `xml:"katalog"`
Toko string `xml:"toko,attr"`
Buku []Buku `xml:"buku"`
}
func main() {
k := Katalog{
Toko: "Toko Buku Sejahtera",
Buku: []Buku{
{
ID: "B-01",
Judul: "Belajar Go",
Penulis: "Budi Santoso",
Harga: Harga{MataUang: "IDR", Nilai: "95000"},
Rating: 5,
},
{
ID: "B-02",
Judul: "Go untuk Data",
Penulis: "Siti Aminah",
Harga: Harga{MataUang: "IDR", Nilai: "120000"},
Rating: 4,
},
},
}
data, err := xml.MarshalIndent(k, "", " ")
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
sumber := []byte(`<katalog toko="Toko Buku Sejahtera">
<buku id="B-03">
<judul>Concurrency di Go</judul>
<penulis>Rina Wijaya</penulis>
<harga mata_uang="IDR">150000</harga>
</buku>
</katalog>`)
var k2 Katalog
if err := xml.Unmarshal(sumber, &k2); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: %+v\n", k2)
for _, b := range k2.Buku {
fmt.Printf("buku %s: %s oleh %s, harga %s %s\n", b.ID, b.Judul, b.Penulis, b.Harga.Nilai, b.Harga.MataUang)
}
}Output yang Dihasilkan:
<katalog toko="Toko Buku Sejahtera">
<buku id="B-01">
<judul>Belajar Go</judul>
<penulis>Budi Santoso</penulis>
<harga mata_uang="IDR">95000</harga>
</buku>
<buku id="B-02">
<judul>Go untuk Data</judul>
<penulis>Siti Aminah</penulis>
<harga mata_uang="IDR">120000</harga>
</buku>
</katalog>
hasil decode: {XMLName:{Space: Local:katalog} Toko:Toko Buku Sejahtera Buku:[{ID:B-03 Judul:Concurrency di Go Penulis:Rina Wijaya Harga:{MataUang:IDR Nilai:150000} Rating:0}]}
buku B-03: Concurrency di Go oleh Rina Wijaya, harga 150000 IDRMari kita telaah baris demi baris program di atas:
- Baris 8-11 (type Harga): field
MataUangmemakai tagmata_uang,attrsehingga menjadi atribut elemenharga, sedangkanNilaimemakai,chardatasehingga menjadi teks di dalam elemenhargaitu sendiri. - Baris 13-19 (type Buku): field
IDmenjadi atributidpada elemenbuku; fieldJudul,Penulis, danHargamenjadi elemen anak; fieldRatingbertagxml:"-"tidak pernah ikut ke XML. - Baris 21-25 (type Katalog): field
XMLName xml.Namedengan tagkatalogmenentukan nama elemen akar,Tokomenjadi atribut akar, dan[]Bukumenjadi deretan elemenbukuberulang. - Baris 48-53:
xml.MarshalIndentmengubah struct menjadi teks XML berindentasi dua spasi lalu mencetaknya. - Baris 55-61 (sumber): dokumen XML masukan untuk arah sebaliknya.
- Baris 63-68:
xml.Unmarshal(sumber, &k2)mengisi struct lewat pointer, termasuk atribut dan chardata yang dipetakan balik ke field asalinya. - Baris 69-71: loop mencetak isi struct hasil decode untuk membuktikan atribut, elemen anak, dan chardata kembali ke tempat semula.
Membaca XML dengan Unmarshal #
xml.Unmarshal memerlukan seluruh dokumen sekaligus dan mengabaikan atribut serta elemen yang tidak dikenal struct tujuan — perilaku yang sama dengan json.Unmarshal. Elemen berulang seperti banyak <buku> dikumpulkan ke field slice. Ketika dokumen sangat besar, xml.NewDecoder menyediakan mode stream dengan Token() atau DecodeElement() agar kita memroses satu elemen pada satu waktu tanpa memuat seluruh dokumen ke memori.
Struct Tag JSON dan XML Bekerja Berdampingan #
Satu struct boleh memuat tag untuk beberapa format sekaligus, misalnya `json:"judul" xml:"judul"`. Package encoding/json hanya membaca bagian json:"...", dan package encoding/xml hanya membaca bagian xml:"...", sehingga kedua penamaan tidak saling ganggu. Materi lengkap tentang penulisan banyak format dalam satu struct tag dibahas pada chapter Struct Tag
.
Referensi #
- Dokumentasi package encoding/xml — referensi resmi seluruh API XML.
- Dokumentasi xml.Marshal — aturan pemetaan struct ke elemen XML.
- Blog Go: JSON and Go — perbandingan gaya encoding pada standard library.
Format tabular satu baris satu rekam data punya tempat tersendiri di dunia data: CSV, yang kita bahas dengan package encoding/csv pada chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
xml-demo.go
Unduh berkaspackage main
import (
"encoding/xml"
"fmt"
)
type Harga struct {
MataUang string `xml:"mata_uang,attr"`
Nilai string `xml:",chardata"`
}
type Buku struct {
ID string `xml:"id,attr"`
Judul string `xml:"judul"`
Penulis string `xml:"penulis"`
Harga Harga `xml:"harga"`
Rating int `xml:"-"` // tidak pernah ikut ke XML
}
type Katalog struct {
XMLName xml.Name `xml:"katalog"`
Toko string `xml:"toko,attr"`
Buku []Buku `xml:"buku"`
}
func main() {
k := Katalog{
Toko: "Toko Buku Sejahtera",
Buku: []Buku{
{
ID: "B-01",
Judul: "Belajar Go",
Penulis: "Budi Santoso",
Harga: Harga{MataUang: "IDR", Nilai: "95000"},
Rating: 5,
},
{
ID: "B-02",
Judul: "Go untuk Data",
Penulis: "Siti Aminah",
Harga: Harga{MataUang: "IDR", Nilai: "120000"},
Rating: 4,
},
},
}
data, err := xml.MarshalIndent(k, "", " ")
if err != nil {
fmt.Println("marshal gagal:", err)
return
}
fmt.Println(string(data))
sumber := []byte(`<katalog toko="Toko Buku Sejahtera">
<buku id="B-03">
<judul>Concurrency di Go</judul>
<penulis>Rina Wijaya</penulis>
<harga mata_uang="IDR">150000</harga>
</buku>
</katalog>`)
var k2 Katalog
if err := xml.Unmarshal(sumber, &k2); err != nil {
fmt.Println("unmarshal gagal:", err)
return
}
fmt.Printf("hasil decode: %+v\n", k2)
for _, b := range k2.Buku {
fmt.Printf("buku %s: %s oleh %s, harga %s %s\n", b.ID, b.Judul, b.Penulis, b.Harga.Nilai, b.Harga.MataUang)
}
}