1. Gambaran umum tentang pustaka standar encoding/json
Bahasa Go menyediakan pustaka encoding/json
yang kuat untuk menangani format data JSON. Dengan pustaka ini, Anda dapat dengan mudah mengonversi tipe data Go ke format JSON (serialisasi) atau mengonversi data JSON ke tipe data Go (deserialisasi). Pustaka ini menyediakan banyak fungsionalitas seperti encoding, decoding, streaming IO, dan dukungan untuk logika parsing JSON kustom.
Tipe data dan fungsi yang paling penting dalam pustaka ini meliputi:
-
Marshal
danMarshalIndent
: digunakan untuk membuat serialisasi tipe data Go menjadi string JSON. -
Unmarshal
: digunakan untuk deserialisasi string JSON menjadi tipe data Go. -
Encoder
danDecoder
: digunakan untuk streaming IO data JSON. -
Valid
: digunakan untuk memeriksa apakah string yang diberikan adalah format JSON yang valid.
Kita akan belajar secara khusus mengenai penggunaan fungsi dan tipe data ini di bab-bab berikutnya.
2. Mengonversi Struktur Data Go ke JSON
2.1 Menggunakan json.Marshal
json.Marshal
adalah fungsi yang mengonversi tipe data Go menjadi string JSON. Fungsi ini mengambil tipe data dari bahasa Go sebagai input, mengonversinya ke format JSON, dan mengembalikan potongan byte bersama dengan kemungkinan kesalahan.
Berikut adalah contoh sederhana yang menunjukkan bagaimana mengonversi sebuah struktur data Go ke string JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{"Alice", 30}
jsonData, err := json.Marshal(person)
if err != nil {
log.Fatalf("Gagal melakukan marshaling JSON: %s", err)
}
fmt.Println(string(jsonData)) // Output: {"name":"Alice","age":30}
}
Selain dari struktur, fungsi json.Marshal
juga dapat melakukan serialisasi tipe data lain seperti map dan slice. Berikut adalah contoh menggunakan map[string]interface{}
dan slice
:
// Mengonversi map ke JSON
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... penanganan kesalahan dan keluaran dihilangkan ...
// Mengonversi slice ke JSON
mySlice := []string{"Apel", "Pisang", "Ceri"}
jsonData, err := json.Marshal(mySlice)
// ... penanganan kesalahan dan keluaran dihilangkan ...
2.2 Tag Struktur
Di dalam Go, tag struktur digunakan untuk memberikan metadata untuk bidang-bidang struktur, mengontrol perilaku serialisasi JSON. Kasus penggunaan paling umum mencakup pengubahan nama bidang, mengabaikan bidang, dan serialisasi bersyarat.
Sebagai contoh, Anda dapat menggunakan tag json:"<nama>"
untuk menentukan nama dari bidang JSON:
type Hewan struct {
NamaSpesies string `json:"spesies"`
Deskripsi string `json:"desc,omitempty"`
Tag string `json:"-"` // Menambahkan tag "-" menandakan bidang ini tidak akan di-serialisasi
}
Pada contoh di atas, tag json:"-"
di depan bidang Tag
memberitahu json.Marshal
untuk mengabaikan bidang ini. Opsi omitempty
untuk bidang Deskripsi
menunjukkan bahwa jika bidang tersebut kosong (nilai nol, seperti string kosong), maka tidak akan disertakan dalam JSON yang ter-serialisasi.
Berikut adalah contoh lengkap penggunaan tag struktur:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Hewan struct {
NamaSpesies string `json:"spesies"`
Deskripsi string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
hewan := Hewan{
NamaSpesies: "Gajah Afrika",
Deskripsi: "Mamalia besar dengan belalai dan gading.",
Tag: "terancam punah", // Bidang ini tidak akan di-serialisasi ke JSON
}
jsonData, err := json.Marshal(hewan)
if err != nil {
log.Fatalf("Gagal melakukan marshaling JSON: %s", err)
}
fmt.Println(string(jsonData)) // Output: {"spesies":"Gajah Afrika","desc":"Mamalia besar dengan belalai dan gading."}
}
Dengan cara ini, Anda dapat memastikan struktur data yang jelas sambil mengontrol representasi JSON, menangani secara fleksibel berbagai kebutuhan serialisasi.
3. Deserialisasi JSON ke Struktur Data Go
3.1 Menggunakan json.Unmarshal
Fungsi json.Unmarshal
memungkinkan kita untuk mengurai string JSON ke dalam struktur data Go seperti struk, peta, dll. Untuk menggunakan json.Unmarshal
, kita pertama-tama perlu mendefinisikan struktur data Go yang cocok dengan data JSON.
Misalkan kita memiliki data JSON berikut:
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
Untuk mengurai data ini ke dalam struktur Go, kita perlu mendefinisikan struktur yang cocok:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
Sekarang kita dapat menggunakan json.Unmarshal
untuk deserialisasi:
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error saat melakukan unmarshalling JSON:", err)
return
}
fmt.Printf("User: %+v\n", user)
}
Pada contoh di atas, kami menggunakan tag seperti json:"name"
untuk memberi tahu fungsi json.Unmarshal
tentang pemetaan bidang JSON ke bidang struktur.
3.2 Parsing Dinamis
Terkadang, struktur JSON yang perlu kita parsing tidak diketahui sebelumnya, atau struktur data JSON dapat berubah secara dinamis. Dalam kasus seperti itu, kita dapat menggunakan interface{}
atau json.RawMessage
untuk parsing.
Menggunakan interface{}
memungkinkan kita untuk parsing tanpa mengetahui struktur JSON:
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// Asersi tipe, pastikan cocok sebelum digunakan
name := result["name"].(string)
fmt.Println("Nama:", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // Catatan: angka dalam interface{} diperlakukan sebagai float64
fmt.Println("Usia:", age)
}
Menggunakan json.RawMessage
memungkinkan kita untuk mempertahankan JSON asli sambil secara selektif melakukan parsing bagian-bagiannya:
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("Nama:", user.Name)
fmt.Println("Usia:", details["age"])
fmt.Println("Pekerjaan:", details["job"])
}
Pendekatan ini berguna untuk menangani struktur JSON di mana beberapa bidang dapat berisi berbagai jenis data, dan memungkinkan penanganan data yang fleksibel.
4 Mengelola Struktur Bertingkat dan Array
4.1 Objek JSON Bertingkat
Data JSON umumnya tidak datar, tetapi mengandung struktur bertingkat. Dalam Go, kita dapat menangani situasi ini dengan mendefinisikan struktur bertingkat.
Misalkan kita memiliki JSON bertingkat berikut:
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
Kita dapat mendefinisikan struktur Go sebagai berikut:
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
Operasi deserialisasi mirip dengan struktur non-bertingkat:
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error saat melakukan unmarshalling JSON:", err)
}
fmt.Printf("%+v\n", user)
}
4.2 JSON Arrays (Array JSON)
Pada JSON, array adalah struktur data yang umum. Di Go, array tersebut sesuai dengan slice.
Pertimbangkan array JSON berikut:
[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]
Di Go, kita mendefinisikan struct dan slice yang sesuai sebagai berikut:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]`
var people []Person
json.Unmarshal([]byte(jsonData), &people)
for _, person := range people {
fmt.Printf("%+v\n", person)
}
}
Dengan cara ini, kita dapat melakukan deserialisasi setiap elemen dalam array JSON menjadi slice dari struct Go untuk pemrosesan dan akses lebih lanjut.
5 Penanganan Kesalahan (Error Handling)
Ketika menangani data JSON, baik itu proses serialisasi (mengkonversi data terstruktur ke format JSON) atau deserialisasi (mengkonversi JSON kembali ke data terstruktur), kesalahan dapat terjadi. Selanjutnya, kita akan membahas kesalahan umum dan bagaimana cara menanganinya.
5.1 Penanganan Kesalahan Serialisasi (Serialization Error Handling)
Kesalahan serialisasi biasanya terjadi selama proses mengonversi struct atau tipe data lain menjadi string JSON. Sebagai contoh, jika kita mencoba untuk melakukan serialisasi terhadap struct yang mengandung field ilegal (seperti tipe channel atau fungsi yang tidak dapat direpresentasikan dalam JSON), json.Marshal
akan mengembalikan sebuah kesalahan.
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// Diasumsikan ada field di sini yang tidak dapat diserialisasi
// Data chan struct{} // Channel tidak dapat direpresentasikan dalam JSON
}
func main() {
u := User{
Name: "Alice",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("Gagal melakukan serialisasi JSON: %v", err)
}
fmt.Println(string(bytes))
}
Pada contoh di atas, kita dengan sengaja mengomentari bagian field Data
. Jika tidak dikomentari, serialisasi akan gagal, dan program akan mencatat kesalahannya dan menghentikan eksekusi. Penanganan kesalahan semacam ini umumnya melibatkan pemeriksaan kesalahan dan implementasi strategi penanganan kesalahan yang sesuai (seperti mencatat kesalahan, mengembalikan data default, dll.).
5.2 Penanganan Kesalahan Deserialisasi (Deserialization Error Handling)
Kesalahan deserialisasi dapat terjadi selama proses mengonversi string JSON kembali menjadi struct Go atau tipe data lainnya. Sebagai contoh, jika format string JSON tidak benar atau tidak kompatibel dengan tipe target, json.Unmarshal
akan mengembalikan sebuah kesalahan.
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"Alice","age":"unknown"}`) // "age" seharusnya berupa bilangan bulat, tetapi di sini disediakan dalam bentuk string
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("Gagal melakukan deserialisasi JSON: %v", err)
}
fmt.Printf("%+v\n", u)
}
Pada contoh kode ini, kita dengan sengaja memberikan tipe data yang salah untuk field age
(sebuah string alih-alih bilangan bulat yang diharapkan), menyebabkan json.Unmarshal
mengembalikan kesalahan. Oleh karena itu, kita perlu menangani situasi ini secara tepat. Praktik umumnya adalah mencatat pesan kesalahan dan, tergantung pada skenario, mungkin mengembalikan objek kosong, nilai default, atau pesan kesalahan.
6 Fitur Lanjutan dan Optimisasi Kinerja
6.1 Penyesuaian Marshal dan Unmarshal
Secara default, paket encoding/json
di Go melakukan serialisasi dan deserialisasi JSON melalui refleksi. Namun, kita dapat menyesuaikan proses-proses ini dengan mengimplementasikan antarmuka json.Marshaler
dan json.Unmarshaler
.
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
Di sini, kita telah mendefinisikan tipe Color
dan mengimplementasikan metode MarshalJSON
dan UnmarshalJSON
untuk mengonversi warna RGB ke tipe string heksadesimal dan kemudian kembali ke warna RGB.
6.2 Pengekoder dan Penguraian
Ketika menangani data JSON besar, penggunaan langsung json.Marshal
dan json.Unmarshal
dapat menyebabkan konsumsi memori berlebih atau operasi input/output yang tidak efisien. Oleh karena itu, paket encoding/json
di Go menyediakan tipe Encoder
dan Decoder
, yang dapat memproses data JSON secara streaming.
6.2.1 Menggunakan json.Encoder
json.Encoder
dapat langsung menulis data JSON ke objek apa pun yang mengimplementasikan antarmuka io.Writer, artinya Anda dapat mengkodekan data JSON langsung ke file, koneksi jaringan, dll.
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Kesalahan Encoding: %v", err)
}
}
6.2.2 Menggunakan json.Decoder
json.Decoder
dapat langsung membaca data JSON dari objek apa pun yang mengimplementasikan antarmuka io.Reader, mencari dan memparsing objek dan larik JSON.
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Kesalahan Penguraian: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
Dengan memproses data dengan pengode dan pengura, Anda dapat melakukan pemrosesan JSON saat membaca, mengurangi penggunaan memori, dan meningkatkan efisiensi pemrosesan, yang sangat berguna untuk penanganan transfer jaringan atau file besar.