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 tagPeran fieldContoh 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.

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:

go
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:

text
<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 IDR

Mari kita telaah baris demi baris program di atas:

  1. Baris 8-11 (type Harga): field MataUang memakai tag mata_uang,attr sehingga menjadi atribut elemen harga, sedangkan Nilai memakai ,chardata sehingga menjadi teks di dalam elemen harga itu sendiri.
  2. Baris 13-19 (type Buku): field ID menjadi atribut id pada elemen buku; field Judul, Penulis, dan Harga menjadi elemen anak; field Rating bertag xml:"-" tidak pernah ikut ke XML.
  3. Baris 21-25 (type Katalog): field XMLName xml.Name dengan tag katalog menentukan nama elemen akar, Toko menjadi atribut akar, dan []Buku menjadi deretan elemen buku berulang.
  4. Baris 48-53: xml.MarshalIndent mengubah struct menjadi teks XML berindentasi dua spasi lalu mencetaknya.
  5. Baris 55-61 (sumber): dokumen XML masukan untuk arah sebaliknya.
  6. Baris 63-68: xml.Unmarshal(sumber, &k2) mengisi struct lewat pointer, termasuk atribut dan chardata yang dipetakan balik ke field asalinya.
  7. 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 #

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 berkas
go
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)
	}
}