Serialisasi, bahasa bersama mesin-mesin.
Klien JavaScript dan server Rust tidak punya kesamaan apa pun di level tipe data. Serialisasi adalah cara mereka tetap bicara: keduanya mengonversi data natifnya ke dan dari satu format yang disepakati untuk perjalanan lewat jaringan. Manual ini mencakup seluruh gagasannya, hambatan bahasa, standar bersama, format teks vs biner, JSON secara mendalam, model mental OSI, dan round-trip penuh klien<->server, dengan kode jadi di Go dan Python.
Hambatan Bahasa
Aplikasi web yang khas adalah dua mesin di tempat berbeda, terhubung lewat internet. Klien: frontend-nya, sering aplikasi JavaScript (React, Angular, Vue) yang berjalan di browser seperti Chrome, bicara ke server, backend-nya, yang mungkin berjalan di localhost atau di tempat jauh di AWS, GCP, atau Azure. Mereka berkomunikasi lewat protokol jaringan: HTTP (REST API tradisional), gRPC, atau WebSocket. Manual ini berasumsi HTTP/REST, karena itu yang masih paling umum.
Inilah jebakannya. Klien bisa jadi JavaScript dan server bisa jadi Rust, dan kedua bahasa itu memperlakukan data dengan cara yang sangat berbeda:
- JavaScript dinamis dan tidak dikompilasi, tipe-nya longgar dan diputuskan saat runtime.
- Rust dikompilasi dan sangat ketat soal tipe.
Jadi saat klien JavaScript mengirim objek seperti { name: "..." } di body request, server Rust tidak bisa memahami secara natif struktur data JavaScript, tipe datanya sendiri tidak mirip sama sekali dengan milik JavaScript. Masalah yang sama terjadi terbalik untuk respons. Dua mesin, dua dunia internal yang tak kompatibel, satu kabel di antara keduanya.
Serialize & Deserialize
Solusinya: kedua sisi menyepakati satu format standar yang umum untuk data di kabel. Setiap mesin mengonversi ke dalam format itu sebelum mengirim, dan keluar dari format itu setelah menerima.
Mengonversi data natif (objek JavaScript, struct Rust, struct Go, objek Python) ke dalam format standar bersama sebelum mengirimnya lewat jaringan (atau menyimpannya).
Kebalikan persisnya: mengambil data dalam format bersama dan mem-parse-nya kembali ke tipe data natif mesin, supaya ia bisa menjalankan logika bisnisnya.
Karena formatnya netral, mesin mana pun bisa bicara ke mesin mana pun apa pun bahasa atau teknologi di bawahnya. "Language-agnostic" berarti tidak peduli Anda JS atau Rust. "Domain-agnostic" berarti tidak peduli lingkungan atau tumpukan teknologi yang Anda jalankan.
Gagasan konversi ke-dan-dari-format-bersama yang sama juga berlaku untuk penyimpanan: menulis data ke disk atau file config/log lalu membacanya kembali. Transmisi yang jadi fokus di sini, tapi fenomenanya sama.
Menyepakati Standar Bersama
Jika masalah ini diserahkan kepada Anda dalam keadaan dingin, dua mesin di lokasi berbeda, terhubung lewat internet, yang harus bertukar data secara language-agnostic, solusi yang jelas adalah mendefinisikan standar bersama.
Hanya istilah keren untuk sekumpulan aturan yang disepakati tentang cara data harus ditulis. Klien dan server sama-sama berjanji: "kami akan mengirim dan menerima data berbentuk seperti ini".
Dengan kesepakatan itu, klien mengonversi logika natifnya (misalnya dari JavaScript) ke dalam standar sebelum mengirim; server membaca standar itu dan mengonversinya ke tipe natifnya sendiri (misalnya struct Rust); ia bekerja, mengonversi respons kembali ke standar, dan mengirimnya; klien mem-parse-nya kembali ke JavaScript. Round-trip itu adalah serialisasi dan deserialisasi. Dalam satu kalimat: mengonversi data ke dan dari format bersama saat transmisi (atau penyimpanan), supaya bisa dipahami lintas bahasa dan domain.
Format Berbasis Teks vs Biner
Banyak sekali standar serialisasi, tapi mereka jatuh ke dua keluarga.
- Format berbasis teks: bisa dibaca manusia. JSON, YAML, dan XML. Anda bisa membuka payload-nya dan membacanya.
- Format biner: dikompilasi menjadi biner mentah untuk transmisi yang sangat efisien dan ringkas. Protobuf (Protocol Buffers) dan Avro yang populer. Tidak dimaksudkan dibaca mata.
YAML dan XML masih dipakai untuk serialisasi, tapi tidak begitu sering untuk komunikasi HTTP. Untuk HTTP REST API tradisional, pilihan bawaan adalah JSON.
JSON: Standar Industri
Untuk komunikasi HTTP REST API tradisional, JSON adalah standar serialisasi paling populer, dipakai kira-kira 80% dari waktu.
JavaScript Object Notation. Dinamai begitu karena tampak dan berperilaku mirip objek JavaScript, tapi meski namanya begitu ia tidak terbatas pada JavaScript dan dipakai di mana-mana, di semua bahasa.
Kenapa ia menang
- Bisa dibaca manusia. JSON dirancang untuk dibaca orang; Anda bisa melirik payload dan langsung mengerti. Itu salah satu daya jual terkuatnya.
- Tipe data fundamental. Tipe nilainya memetakan dengan rapi ke konsep yang sudah diketahui setiap programmer.
Di mana Anda akan menjumpainya
- Transmisi HTTP: body request/response REST API (fokus di sini).
- Logging: pilihan yang sangat populer untuk log aplikasi dan server saat runtime.
- File konfigurasi: config aplikasi dan tool.
Aturan Sintaks JSON
JSON punya sangat sedikit aturan, itulah intinya. Ini contoh objek khas, dengan anotasi untuk setiap aturan yang disebut sumber:
Aturannya, dijabarkan
- Objek JSON dimulai { dan diakhiri }.
- Key harus string yang dibungkus tanda kutip ganda: mis. "name":. Tidak ada tipe data lain yang bisa jadi key. (Kutip ganda, selalu, bukan kutip tunggal.)
- Nilai terbatas pada tipe fundamental: string, angka, boolean, larik, atau objek bertingkat lain.
- Objek bertingkat mengikuti aturan yang sama persis secara rekursif: kurung kurawal, key string berkutip, dan nilai bertipe fundamental yang sama. JSON bisa menyarang sedalam yang Anda suka.
Tidak banyak lagi yang perlu dihafal. Kurung kurawal membungkus objek, key string berkutip ganda, dan nilai berupa string, angka, boolean, larik, atau objek bertingkat, tiap objek bertingkat menaati karakteristik yang sama. Itu saja isi JSON.
Model Mental OSI
Saat data melintasi internet ia melewati banyak lapisan jaringan, model OSI. Anda tidak perlu menguasai paket IP dan frame data untuk jadi engineer backend; gambaran tingkat tinggi cukup. Dua ujung tumpukan adalah lapisan aplikasi (atas) dan lapisan fisik (bawah); ada beberapa lapisan di antaranya, dan tumpukan yang sama ada di klien dan server.
Anda hanya fokus pada lapisan aplikasi. Anggap klien Anda men-serialize data ke JSON lalu mengirimnya. Di balik layar jaringan mengubah JSON itu menjadi frame data -> paket IP -> bit fisik (0 dan 1) yang dikirim sebagai sinyal listrik atau optik. Sebelum server Anda menyentuh data, jaringan merakit ulang bit-bit itu menjadi JSON yang sama persis dengan yang dikirim klien. Anda tidak pernah berurusan dengan konversi perantara, hanya JSON-nya.
Alur Kerja End-to-End
Merangkai setiap bagian, inilah round-trip lengkap dalam request sungguhan, fenomena yang secara utuh adalah serialisasi & deserialisasi.
Dalam kata-kata: klien mengumpulkan input, men-serialize-nya menjadi string JSON di body request, lalu mengirimkannya; bit-bit melintasi internet; server men-deserialize JSON menjadi tipe natifnya, menjalankan logika bisnis (mis. menyimpan ke database), men-serialize responsnya kembali ke JSON, dan mengirimnya; klien men-deserialize JSON itu menjadi objek JavaScript dan memperbarui UI. Seluruh alur itulah yang kita sebut serialisasi dan deserialisasi, fenomena yang sebagian besar cukup Anda ketahui keberadaannya; ia punya sangat sedikit konsep sendiri.
Serialisasi di Go
Go men-serialize ke/dari JSON dengan paket standar encoding/json: Marshal = serialize, Unmarshal = deserialize. Pilar-pilarnya muncul lewat interface (abstraksi + polimorfisme), field tak-terekspor + struct tag (enkapsulasi), dan embedding (komposisi, inheritance ala Go). Pilar ditandai tag OOP.
package main import ("encoding/json"; "fmt"; "time") // ABSTRACTION: Serializer names a capability, "turn data into // bytes and back", without binding to a concrete format. Code // depends on this contract, so JSON could be swapped for another // format with zero changes to callers. type Serializer interface { Serialize(v any) ([]byte, error) // native -> common format Deserialize(data []byte, v any) error // common format -> native } // POLYMORPHISM: JSONSerializer implements Serializer. Any other // format (YAML, Protobuf) could implement the same interface and // be used interchangeably through a Serializer variable. type JSONSerializer struct{} func (JSONSerializer) Serialize(v any) ([]byte, error) { return json.Marshal(v) // SERIALIZE: struct -> JSON bytes } func (JSONSerializer) Deserialize(data []byte, v any) error { return json.Unmarshal(data, v) // DESERIALIZE: JSON bytes -> struct } // "INHERITANCE" via COMPOSITION: BaseModel holds shared fields; // embedding it into User reuses them (and their json tags). type BaseModel struct { ID int `json:"id"` // tag = the JSON key name CreatedAt time.Time `json:"created_at"` } // ENCAPSULATION: an exported struct whose JSON shape is controlled // by tags. "password" is unexported (lowercase) -> private AND // invisible to the JSON encoder, so it never leaks over the wire. type User struct { BaseModel // embedded -> inherits ID, CreatedAt Name string `json:"name"` Active bool `json:"active"` Address Address `json:"address"` // nested object password string // unexported: hidden from JSON output } type Address struct { Country string `json:"country"` Phone int `json:"phone"` } func main() { var codec Serializer = JSONSerializer{} // program to the interface u := User{ BaseModel: BaseModel{ID: 1, CreatedAt: time.Now()}, Name: "Ada", Active: true, Address: Address{Country: "India", Phone: 123456}, } // SERIALIZE, native Go struct into the common JSON format out, _ := codec.Serialize(u) fmt.Println(string(out)) // {"id":1,"created_at":"...","name":"Ada","active":true, // "address":{"country":"India","phone":123456}} // DESERIALIZE, JSON received over HTTP back into a Go struct incoming := []byte(`{"name":"Lin","address":{"country":"IN","phone":42}}`) var back User codec.Deserialize(incoming, &back) fmt.Println(back.Name, back.Address.Country) // Lin IN }
Serialisasi di Python
Python memakai modul json bawaan: json.dumps = serialize (dump ke string), json.loads = deserialize (load dari string). Arsitektur OOP yang sama: kontrak ABC, inheritance, polimorfisme, dan atribut privat.
import json from abc import ABC, abstractmethod from dataclasses import dataclass, asdict, field # ABSTRACTION: Serializer is an Abstract Base Class. The # @abstractmethod forces subclasses to implement both directions. # You cannot instantiate Serializer itself, it is a pure contract. class Serializer(ABC): @abstractmethod def serialize(self, obj) -> str: ... # native -> common @abstractmethod def deserialize(self, data: str): ... # common -> native # INHERITANCE + POLYMORPHISM: JSONSerializer IS-A Serializer and # overrides both methods. A YamlSerializer could subclass the same # ABC and be dropped in wherever a Serializer is expected. class JSONSerializer(Serializer): def serialize(self, obj) -> str: return json.dumps(obj) # SERIALIZE: dict -> JSON text def deserialize(self, data: str): return json.loads(data) # DESERIALIZE: JSON text -> dict # INHERITANCE: BaseModel is a shared parent (id, timestamps). @dataclass class BaseModel: id: int = 0 @dataclass class Address: country: str phone: int # nested object # ENCAPSULATION: __password is name-mangled (-> _User__password), # effectively private, and to_dict() chooses what leaves the # object, the secret never appears in the serialized output. @dataclass class User(BaseModel): # IS-A BaseModel name: str = "" active: bool = True address: Address | None = None __password: str = "" # private; excluded below def to_dict(self) -> dict: d = asdict(self) d.pop("_User__password", None) # keep the secret out return d if __name__ == "__main__": codec: Serializer = JSONSerializer() # program to the contract user = User(id=1, name="Ada", address=Address("India", 123456)) # SERIALIZE, native object into the common JSON format payload = codec.serialize(user.to_dict()) print(payload) # {"id": 1, "name": "Ada", "active": true, # "address": {"country": "India", "phone": 123456}} # DESERIALIZE, JSON received over HTTP back into native data incoming = '{"name": "Lin", "address": {"country": "IN", "phone": 42}}' data = codec.deserialize(incoming) print(data["name"], data["address"]["country"]) # Lin IN
Keduanya mendefinisikan kontrak Serializer (abstraksi), mengimplementasikannya untuk JSON (polimorfisme), berbagi field lewat base model (inheritance / komposisi), dan menjaga rahasia keluar dari format kabel (enkapsulasi). Kata kerjanya universal: serialize = natif -> JSON (Go Marshal, Python dumps); deserialize = JSON -> natif (Go Unmarshal, Python loads).
Glosarium
Setiap istilah dari sumber, dalam satu tempat.
| Istilah | Makna |
|---|---|
| Serialization | Mengonversi data natif (objek/struct) ke format standar bersama untuk dikirim atau disimpan. |
| Deserialization | Kebalikannya, mem-parse format bersama kembali ke tipe data natif mesin. |
| Klien / frontend | Aplikasi yang mengirim request, sering aplikasi JavaScript di browser. |
| Server / backend | Mesin yang menerima request dan menjalankan logika bisnis (mis. aplikasi Rust). |
| Tipe data natif | Representasi internal milik sebuah bahasa, objek JS, struct Rust/Go, objek Python. |
| Parse | Membaca sebuah format dan mengubahnya menjadi sesuatu yang bisa dipakai mesin. |
| Standar / format bersama | Sekumpulan aturan yang disepakati untuk cara data ditulis, supaya kedua sisi memahaminya. |
| Language-agnostic | Berfungsi apa pun bahasa pemrograman yang dipakai tiap sisi. |
| Domain-agnostic | Berfungsi apa pun lingkungan / tumpukan teknologi di tiap sisi. |
| Format berbasis teks | Serialisasi yang bisa dibaca manusia, JSON, YAML, XML. |
| Format biner | Serialisasi yang ringkas, efisien, tak terbaca, Protobuf, Avro. |
| JSON | JavaScript Object Notation; pilihan ±80% untuk HTTP REST; bisa dibaca manusia; bukan khusus JS. |
| Key / value | Key JSON berupa string berkutip; nilai berupa string, angka, boolean, larik, atau objek bertingkat. |
| Objek bertingkat | Objek JSON di dalam objek lain, mengikuti aturan yang sama secara rekursif. |
| Model OSI | Model berlapis komunikasi jaringan, dari lapisan aplikasi sampai lapisan fisik. |
| Lapisan aplikasi | Lapisan teratas, tempat Anda, engineer backend, bekerja dalam JSON. |
| Lapisan fisik | Lapisan terbawah, bit mentah (0 dan 1) sebagai sinyal listrik/optik. |
| Frame data / paket IP | Bentuk antara yang diambil data antara lapisan aplikasi dan fisik. |
Dua mesin dengan tipe data natif yang tidak kompatibel menyepakati format bersama (biasanya JSON). Masing-masing men-serialize datanya ke format itu sebelum mengirim dan men-deserialize-nya kembali setelah menerima, jadi komunikasi jadi language- dan domain-agnostic. Jaringan mengubah JSON menjadi bit dan kembali; di lapisan aplikasi Anda hanya berurusan dengan JSON.