tutorial

50 Protobuf JSON Marshal dan Unmarshal

Jika kamu pernah bekerja dengan microservices, pasti sudah tidak asing lagi dengan Protobuf sebagai serialization format. Dukungan Protobuf terhadap message exchange yang cepat sekaligus schema-typed menjadi alasan utama format ini digandrungi di ekosistem Go dan berbagai bahasa lainnya. Namun, realita di lapangan sering kali menuntut interoperabilitas service-service berbasis JSON ― baik untuk logging, REST API, ataupun debugging. Maka di sinilah fitur marshal dan unmarshal JSON pada Protobuf menjadi sangat penting.

Pada artikel kali ini, kita akan membahas secara komprehensif proses Marshal dan Unmarshal JSON pada Protobuf di Go. Kita akan mengeksplorasi implementasi, simulasi, hingga potensi pitfall yang mesti diwaspadai.


Ringkasan Istilah

IstilahPenjelasan
MarshalMengkonversi (encode) data struktur ke format lain (misal: Protobuf ke JSON)
UnmarshalMengkonversi (decode) data dari format lain ke struktur (misal: JSON ke Protobuf struct)
ProtobufFormat serialisasi biner yang schema-typed dari Google
JSONFormat data berbasis teks yang mudah dibaca manusia

Kenapa Protobuf JSON Marshal/Unmarshal Diperlukan?

Walau Protobuf mengandalkan encoding binary, namun berbagai use-case meminta format JSON. Beberapa situasi seperti:

  1. Logging berbasis JSON untuk konsistensi stack.
  2. REST API yang secara kontrak memerlukan body berformat JSON.
  3. Debugging manual terhadap payload.
  4. Backward compatibility dengan sistem warisan yang masih pakai JSON.

Diagram Alur Proses

Mari kita ilustrasikan alurnya memakai diagram flowchart sederhana.

flowchart LR
    A[Struct Protobuf] --Marshal--> B[JSON]
    B --Unmarshal--> C[Struct Protobuf]

Alur di atas memperjelas bahwa ada proses dua arah dari Protobuf ke JSON (Marshal) dan sebaliknya (Unmarshal).


Setup Protobuf dan Go

Sebelum masuk ke kode, pastikan punya setup berikut:

  • Golang (1.18+)
  • protoc
  • protobuf-go plugin: google.golang.org/protobuf

Contoh .proto file sederhana:

syntax = "proto3";

package article;

message User {
    int32 id = 1;
    string name = 2;
    bool is_active = 3;
}

Generate Go file:

protoc --go_out=. user.proto

Implementasi Marshal dan Unmarshal

1. Marshal ke JSON

google.golang.org/protobuf/encoding/protojson menyediakan utility untuk Marshal dan Unmarshal.

Contoh kode:

package main

import (
    "fmt"
    "log"

    "google.golang.org/protobuf/encoding/protojson"
    pb "path/to/generated/package/article"
)

func main() {
    user := &pb.User{
        Id:       50,
        Name:     "Sophia",
        IsActive: true,
    }

    // Marshal ke JSON
    data, err := protojson.Marshal(user)
    if err != nil {
        log.Fatalf("marshal error: %v", err)
    }
    fmt.Println(string(data))
}

Output:

{"id":50,"name":"Sophia","isActive":true}

Pro Tip:
Output secara default hanya membawa field yang diisi. Untuk output yang lebih readable, gunakan:

marshaller := protojson.MarshalOptions{
    Multiline:       true,
    EmitUnpopulated: true,
    Indent:          "  ",
}
jsonData, _ := marshaller.Marshal(user)
fmt.Println(string(jsonData))

2. Unmarshal dari JSON ke Protobuf

Mari simulasikan proses sebaliknya: JSON ke struct Protobuf.

jsonStr := `{"id":90,"name":"Aston","isActive":false}`

var user pb.User
err := protojson.Unmarshal([]byte(jsonStr), &user)
if err != nil {
    log.Fatalf("unmarshal error: %v", err)
}
fmt.Printf("Unmarshalled: %#v\n", user)

Output:

Unmarshalled: article.User{Id:90, Name:"Aston", IsActive:false, ...}

Simulasi: Menangani Field Kosong

Berikut adalah tabel mapping perilaku marshal/unmarshal terkait field kosong pada Protobuf dan JSON.

Field ProtobufNilaiSetelah Marshal ke JSONUraian
id (int32)0Hilang/Tampil (1)Default hilang, atau tampil jika EmitUnpopulated
name (string)""Hilang/Tampil (1)Sama
is_active (bool)falseHilang/Tampil (1)Sama

(1) Mengatur EmitUnpopulated: true pada opsi Marshal membuat JSON mencantumkan default value.

Contoh Perbandingan:

user := &pb.User{} // semuanya default

defaultOutput, _ := protojson.Marshal(user)
withOpt := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(user)

fmt.Println("Biasa:", string(defaultOutput))
fmt.Println("EmitUnpopulated:", string(withOpt))

Output:

Biasa: {}
EmitUnpopulated: {"id":0,"name":"","isActive":false}

Pitfall dan Best Practices

1. Ekspose Internal Field

  • Field Protobuf tidak semua langsung tampil di JSON, misal field oneof atau map.

2. Nilai Default

  • Nilai default di Protobuf (0, false, "") tidak otomatis muncul di JSON tanpa set EmitUnpopulated.

3. Kompatibilitas

  • JSON Protobuf bisa beda dengan JSON tag struct Go biasa. Misal, field-casing dan enum bisa beda penulisan.

4. Array dan Repeated Field

  • Empty list tetap muncul sebagai [] jika EmitUnpopulated: true.

5. Oneof / Any

  • Pemrosesan oneof dan type Any membutuhkan perhatian khusus. Kadang-kadang lebih baik membuat handler custom.

Skenario Lanjutan: Interop Service

Misalkan salah satu service mengirim JSON ke service lain, lalu dilakukan unmarshal ke Protobuf.

sequenceDiagram
    participant Client
    participant ServiceA
    participant ServiceB

    Client->>ServiceA: Kirim JSON Payload
    ServiceA->>ServiceA: Unmarshal ke Protobuf Struct
    ServiceA->>ServiceB: Marshal ke Binary Protobuf
    ServiceB->>ServiceB: Unmarshal dari Binary

Kesimpulan

Marshal dan Unmarshal antara Protobuf dan JSON di Go sangat mudah dengan bantuan protojson. Namun, seperti layaknya engineer yang teliti, penting untuk memahami default behavior, pengaturan option, serta antagonis kecil seperti field kosong atau distorsi serialisasi.

Bekerjalah dengan schema-first mindset dan pastikan output telah tervalidasi dengan konsumen dari masing-masing format. Pengalaman akan mengajarkan edge case yang tidak selalu ter-cover oleh tutorial standar.

Jika kamu ingin berdiskusi lebih jauh terkait schema evolution dan serialisasi di sistem enterprise, jangan ragu untuk berdiskusi di kolom komentar. Selamat berkarya dan happy marshalling! 🚀


Referensi

comments powered by Disqus

Topik Terhangat

programming
235
tutorial
102
tips-and-trick
43
jaringan
28
hardware
11
linux
4
kubernetes
1