unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP
JSON

JSON #

JSON (JavaScript Object Notation) adalah format pertukaran data yang paling umum digunakan dalam komunikasi antar sistem modern, terutama pada arsitektur REST, microservices, dan komunikasi client-server.

Di Golang, JSON menjadi bagian fundamental dalam pengembangan backend karena:

  • Hampir semua API menggunakan JSON sebagai format request/response
  • Integrasi dengan frontend (React, Vue, Mobile App) menggunakan JSON
  • Komunikasi antar service (internal/external)
  • Penyimpanan semi-structured data

Artikel ini akan membahas:

  1. Cara kerja JSON di Go
  2. Encoding & decoding secara mendalam
  3. Struct tags dan pengaruhnya
  4. Custom marshal & unmarshal
  5. Streaming JSON
  6. Validasi dan error handling
  7. Performance consideration
  8. Best practice produksi

Package encoding/json #

Go menyediakan package standar:

encoding/json

Package ini menggunakan reflection untuk membaca struct dan mengubahnya menjadi JSON atau sebaliknya.

Dua fungsi utama:

  • json.Marshal() → struct ke JSON
  • json.Unmarshal() → JSON ke struct

Encoding (Marshal) JSON #

Contoh Dasar #

type User struct {
    ID   int
    Name string
}

u := User{ID: 1, Name: "Unis"}
b, err := json.Marshal(u)

Output:

{"ID":1,"Name":"Unis"}

MarshalIndent (Pretty Print) #

b, err := json.MarshalIndent(u, "", "  ")

Digunakan untuk logging atau debugging.


Decoding (Unmarshal) JSON #

var u User
err := json.Unmarshal(data, &u)

⚠ Penting: Harus pointer.

Kenapa? Karena Unmarshal akan mengisi nilai ke memori yang sudah dialokasikan.


Struct Tags #

Struct tag menentukan bagaimana field dipetakan.

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

Opsi Struct Tag #

1. Rename Field #

`json:"user_id"`

2. Omit Empty #

`json:"name,omitempty"`

Field tidak akan muncul jika:

  • string kosong
  • 0
  • false
  • nil pointer
  • slice/map kosong

3. Ignore Field #

`json:"-"`

Zero Value vs Nil Problem #

Masalah umum dalam API design:

Bagaimana membedakan:

  • field tidak dikirim
  • field dikirim dengan nilai default (0, false)

Solusi: gunakan pointer.

type UpdateUserRequest struct {
    Name *string `json:"name"`
    Age  *int    `json:"age"`
}

Jika nil → tidak dikirim Jika non-nil → dikirim

Ini penting untuk PATCH endpoint.


Custom Marshal & Unmarshal #

Jika butuh kontrol penuh.

Custom Marshal #

func (u User) MarshalJSON() ([]byte, error) {
    type Alias User
    return json.Marshal(&struct {
        Alias
        Name string `json:"name"`
    }{
        Alias: Alias(u),
        Name:  strings.ToUpper(u.Name),
    })
}

Custom Unmarshal #

func (u *User) UnmarshalJSON(data []byte) error {
    type Alias User
    aux := &struct {
        *Alias
    }{
        Alias: (*Alias)(u),
    }

    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }

    u.Name = strings.TrimSpace(u.Name)
    return nil
}

Digunakan untuk:

  • Format tanggal khusus
  • Enum validation
  • Transformasi nilai
  • Backward compatibility

Handling JSON di HTTP Server #

Contoh sederhana:

func handler(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest

    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid json", http.StatusBadRequest)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

Kenapa NewDecoder dan bukan Unmarshal?

Karena lebih memory efficient dan langsung membaca dari stream.


Streaming JSON #

Jika menangani payload besar.

decoder := json.NewDecoder(r.Body)

for decoder.More() {
    var item Item
    if err := decoder.Decode(&item); err != nil {
        return err
    }
}

Digunakan untuk:

  • Bulk import
  • Event streaming
  • File JSON besar

Unknown Field Handling #

Secara default, field tidak dikenal akan diabaikan.

Untuk strict mode:

decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()

Best practice untuk public API agar client tidak kirim field aneh.


JSON Number Handling #

Default behavior:

Semua number di-decode menjadi float64 jika menggunakan interface{}.

Solusi:

decoder.UseNumber()

Agar menjadi json.Number.


Dynamic JSON (map[string]interface{}) #

Digunakan jika schema tidak tetap.

var data map[string]interface{}
json.Unmarshal(b, &data)

Masalah:

  • Type assertion berantakan
  • float64 untuk semua angka
  • Tidak type-safe

Best practice: hindari kecuali benar-benar perlu.


Validasi JSON #

Biasanya dikombinasikan dengan validator.

Contoh:

  • github.com/go-playground/validator

Flow umum:

  1. Decode
  2. Validate struct
  3. Return error detail

Error Handling Best Practice #

Jangan expose error mentah ke client.

Pattern:

if err := decoder.Decode(&req); err != nil {
    log.Error(err)
    return errorResponse("INVALID_JSON")
}

Pisahkan:

  • internal error
  • public error message

Performance Consideration #

Masalah utama encoding/json:

  • Reflection heavy
  • Allocations tinggi

Alternatif lebih cepat:

  • jsoniter
  • easyjson
  • sonic (bytedance)

Namun tradeoff:

  • Tambah dependency
  • Compatibility
  • Maintainability

Untuk sebagian besar API bisnis, encoding/json sudah cukup.


Common Mistakes #

  1. Lupa pointer di Unmarshal
  2. Tidak menutup Body
  3. Tidak validasi input
  4. Menggunakan map[string]interface{} berlebihan
  5. Tidak membedakan zero value vs missing field
  6. Menggunakan struct tanpa tag JSON
  7. Tidak menggunakan DisallowUnknownFields untuk public API

Design Pattern di Production #

1. Separate DTO dan Domain Model #

Jangan expose struct database langsung.

DTO ↔ Mapper ↔ Domain ↔ Repository

2. Gunakan Pointer untuk PATCH #

3. Gunakan Versioning #

/v1/users
/v2/users

4. Logging JSON Request (hati-hati PII) #


Penutup #

JSON handling di Golang terlihat sederhana, tetapi di production ada banyak detail penting:

  • Pointer vs value
  • Strict decoding
  • Performance
  • Validation
  • Custom marshal

Menguasai JSON di Go bukan hanya soal Marshal dan Unmarshal, tetapi tentang:

  • API design
  • Data consistency
  • Backward compatibility
  • Security
  • Observability

Jika digunakan dengan benar, Go menyediakan ekosistem JSON yang stabil, aman, dan cukup performant untuk sebagian besar sistem backend modern.

« Mocking
YAML »
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact