Perilaku bawaan serde — nama field menjadi kunci JSON apa adanya — nyaman selama kita mengurus data sendiri. Dua tekanan dunia nyata segera merusak kenyamanan tersebut. Pertama, konvensi penamaan: komunitas Rust memakai snake_case untuk field, sementara API web kebanyakan bertukar kunci bergaya camelCase, dan kedua pihak tidak mau berpindah keyakinan. Kedua, kerahasiaan: struct AkunPengguna menyimpan field password untuk keperluan internal program, tetapi nilai tersebut sama sekali tidak boleh ikut tercetak ketika struct di-serialize untuk dikirim ke jaringan. Mengganti nama field di struct atau membuang field dari struct bukanlah solusi — keduanya merusak kode Rust yang sudah ada. Serde menyediakan attribute #[serde(...)] yang menyesuaikan perilaku serialize/deserialize tanpa menyentuh definisi struct sama sekali. Chapter ini membahas dua attribute pertama: rename dengan rename_all untuk penamaan, dan skip untuk penyembunyian field.
rename_all: Menyesuaikan Konvensi Penamaan #
Atribut rename_all dipasang pada struct — posisi tersebut dinamai container attribute karena memberlakukan aturan untuk seluruh isi struct. Nilai "camelCase" memerintahkan serde mengubah semua nama field bergaya snake_case menjadi camelCase pada JSON. Pesanan pembelian dari sistem web modern menjadi contoh yang pas:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DetailPesanan {
nama_produk: String,
harga_satuan: u64,
jumlah_item: u32,
}
fn main() {
let pesanan = DetailPesanan {
nama_produk: String::from("Kopi Torabika"),
harga_satuan: 15000,
jumlah_item: 2,
};
let teks = serde_json::to_string_pretty(&pesanan).unwrap();
println!("{}", teks);
let balik: DetailPesanan = serde_json::from_str(&teks).unwrap();
println!("Total bayar: {}", balik.harga_satuan * balik.jumlah_item as u64);
}Output yang Dihasilkan:
{
"namaProduk": "Kopi Torabika",
"hargaSatuan": 15000,
"jumlahItem": 2
}
Total bayar: 30000Field Rust tetap nama_produk dengan garis bawah, sementara kunci JSON menjadi namaProduk dengan huruf besar di tengah. Mari kita bedah baris-baris pentingnya:
- Baris 4 (
#[serde(rename_all = "camelCase")]): attribute container ini ditempel di antara derive dan definisi struct, memberlakukan konversi nama untuk semua field sekaligus — serialize dan deserialize sama-sama mengikuti. - Baris 5-9 (
struct DetailPesanan { ... }): nama field tetap memakai konvensi Rustsnake_case; kode internal kita tidak perlu berubah gaya hanya karena pihak lain berbeda selera. - Baris 21 (
let balik: DetailPesanan = serde_json::from_str(&teks).unwrap();): round-trip terbukti dua arah — teks dengan kuncinamaProdukberhasil di-parse kembali ke fieldnama_produk, karena pemetaan rename berlaku simetris. - Baris 22 (
println!("Total bayar: {}", ...)): hasil30000membuktikanbalik.harga_satuanterbaca sebagaiu64dan ikut perhitungan aritmetika, bukan sekadar teks.
Selain "camelCase", nilai lain yang sering dipakai antara lain "kebab-case" (nama-produk), "SCREAMING_SNAKE_CASE" (NAMA_PRODUK), dan "PascalCase" (NamaProduk) — semuanya mengikuti konvensi penamaan yang berlaku di sistem tujuan.
rename untuk Satu Field #
Kadang ketidakcocokan nama hanya menimpa satu field — misalnya API lama yang sudah terlanjur memakai kunci "desc" yang singkat, sementara kita ingin field Rust bernama deskripsi yang utuh. Atribut rename ditempel pada field tersebut, bukan pada struct:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Produk {
nama: String,
#[serde(rename = "desc")]
deskripsi: String,
}
fn main() {
let kopi = Produk {
nama: String::from("Kopi Torabika"),
deskripsi: String::from("Robusta dengan proses giling halus"),
};
let teks = serde_json::to_string_pretty(&kopi).unwrap();
println!("Hasil serialize:");
println!("{}", teks);
let balik: Produk = serde_json::from_str(&teks).unwrap();
println!("Field Rust tetap 'deskripsi': {}", balik.deskripsi);
}Output yang Dihasilkan:
Hasil serialize:
{
"nama": "Kopi Torabika",
"desc": "Robusta dengan proses giling halus"
}
Field Rust tetap 'deskripsi': Robusta dengan proses giling halusKunci "desc" muncul di JSON, sementara kode Rust tetap memanggil balik.deskripsi. Mari kita bedah kodenya:
- Baris 6 (
#[serde(rename = "desc")]): field attribute ini mengganti nama kunci untuk fielddeskripsisaja; fieldnamatetap memakai nama aslinya. - Baris 16 (
let teks = serde_json::to_string_pretty(&kopi).unwrap();): hasil serialize memuat kunci"desc"— kontrak JSON mengikuti attribute, bukan nama field Rust. - Baris 20 (
let balik: Produk = serde_json::from_str(&teks).unwrap();): arah deserialize memakai pemetaan yang sama — parser mencari kunci"desc"di JSON dan mengisinya ke fielddeskripsi. Kunci"deskripsi"di JSON justru akan diabaikan.
skip: Menyembunyikan Field dari JSON #
Atribut skip menjawab masalah kerahasiaan. Field password tetap hidup di struct dan dipakai kode internal, tetapi serde seolah tidak melihatnya ketika menulis dan membaca JSON:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct AkunPengguna {
nama: String,
email: String,
#[serde(skip)]
password: String,
}
fn main() {
let akun = AkunPengguna {
nama: String::from("Budi Santoso"),
email: String::from("[email protected]"),
password: String::from("sandi-contoh-123"),
};
let teks = serde_json::to_string_pretty(&akun).unwrap();
println!("JSON yang dikirim ke jaringan:");
println!("{}", teks);
let hasil: AkunPengguna = serde_json::from_str(&teks).unwrap();
println!("Nama : {}", hasil.nama);
println!("Password: '{}'", hasil.password);
}Output yang Dihasilkan:
JSON yang dikirim ke jaringan:
{
"nama": "Budi Santoso",
"email": "[email protected]"
}
Nama : Budi Santoso
Password: ''JSON keluaran hanya memuat dua field — password lenyap dari teks yang berkelana ke jaringan. Mari kita bedah baris-baris pentingnya:
- Baris 7 (
#[serde(skip)]): field attribute ini mengeluarkan fieldpassworddari kedua arah: implementasiSerializetidak menulisnya, dan implementasiDeserializetidak mencarinya di JSON. - Baris 15 (
password: String::from("sandi-contoh-123")): nilai sandi tetap tersimpan di struct di memori — field Rust dan seluruh kode internal tidak terpengaruh attribute ini. - Baris 18 (
let teks = serde_json::to_string_pretty(&akun).unwrap();): hasil JSON berhenti di fieldemail; tidak ada kunci"password"sama sekali, sehingga nilai sandi tidak pernah meninggalkan program. - Baris 22 (
let hasil: AkunPengguna = serde_json::from_str(&teks).unwrap();): parse balik berhasil meskipun JSON tidak memuatpassword— field yang di-skip tidak dianggap hilang, berbeda dengan field biasa yang memicu galatmissing fielddari Chapter sebelumnya. - Baris 24 (
println!("Password: '{}'", hasil.password);): field terisi string kosong''. Karena JSON tidak memuat data untuk field ini, deserializer mengisinya denganDefault::default()milik tipe field —Stringpunya implementasiDefaultberupa string kosong. Tipe field yang di-skip memang harus mengimplementasikanDefaultagar strategi pengisian ini sah.
Atribut satu arah juga tersedia untuk kebutuhan yang lebih halus: skip_serializing hanya menyembunyikan saat menulis JSON, skip_deserializing hanya mengabaikan saat membaca. Pola yang paling sering dipakai di proyek nyata adalah skip_serializing_if = "Option::is_none" pada field Option<T> — field yang bernilai None tidak ditulis ke JSON, namun tetap sah absen saat parsing.
Ringkasan #
Atribut rename_all = "camelCase" menyesuaikan seluruh nama field ke konvensi JSON pihak lain tanpa mengubah gaya penamaan Rust, dan rename mengganti nama kunci untuk satu field tertentu — keduanya bekerja simetris pada serialize dan deserialize. Atribut skip mengeluarkan field dari JSON di kedua arah: nilai tidak pernah tertulis, dan field diisi nilai Default milik tipenya saat parsing. Keluarga skip yang lebih halus seperti skip_serializing_if menangani kasus satu arah. Pemetaan nama dan visibilitas kini terkendali, namun dua kekakuan lain masih menunggu: field yang wajib hadir padahal data kadang tidak lengkap, dan struct bersarang yang harus tampil rata di JSON. Keduanya dijawab attribute default dan flatten pada Chapter berikutnya.