Skip to content
Kembali ke Blog
Tutorial

JSON ke Struct Rust: Buat Struct serde (Panduan 2026)

Ubah JSON menjadi struct Rust: tipe angka (i64/u64/f64), Option untuk null, serde rename untuk camelCase, plus jebakan yang harus dihindari. Gratis.

11 menit membaca

JSON ke Struct Rust: Buat Struct serde (Panduan 2026)

Tempel payload Anda ke konverter JSON ke Struct Rust lalu salin struct serde yang dihasilkannya. Itulah keseluruhan alur kerja JSON ke struct Rust: tidak ada yang perlu dipasang, tidak ada yang diunggah, semuanya berlangsung di browser Anda. Kasus “saya butuh struct sekarang juga” tuntas dalam hitungan detik.

Namun menghasilkan struct dan menghasilkan struct yang benar adalah dua persoalan berbeda. Konverter hanya bisa menebak, dan di sinilah Rust menaikkan taruhannya. Di TypeScript, tebakan yang salah cukup menurun menjadi any longgar yang bisa Anda abaikan. Dengan serde, tebakan yang salah gagal saat deserialisasi (deserialize): float yang dijejalkan ke i64, null tak terduga pada field non-opsional, dan serde_json::from_str yang menyerahkan Err alih-alih data Anda. Itulah sebabnya membuat struct dengan benar lebih penting di Rust dibanding kebanyakan bahasa lain.

Berikut ini cara inferensi sebenarnya bekerja, aturan penentuan tipe angka yang salah dipahami kebanyakan konverter, kapan harus memakai #[serde(rename)] versus #[serde(rename_all)], serta cara menangani tanggal, kunci dinamis, dan kata kunci (keyword) Rust agar keluarannya selalu dapat dikompilasi.

Cara mengonversi JSON ke Rust

Mengonversi JSON ke Rust hanya butuh tiga langkah:

  1. Tempel JSON Anda. Masukkan objek, array, atau respons API mentah ke kotak input. Konversi berjalan seketika dan sepenuhnya di sisi klien (client-side).
  2. Sesuaikan keluaran. Ganti nama struct root, aktifkan/nonaktifkan derive serde, tambahkan Debug dan Clone, atau hilangkan visibilitas pub agar cocok dengan gaya crate Anda.
  3. Salin atau unduh. Ambil kode Rust yang dihasilkan dengan sekali klik lalu tempel langsung ke proyek Anda.

Itulah keseluruhan jalur transaksionalnya. Jika input Anda terminifikasi (minified) atau Anda ragu apakah valid, jalankan dulu lewat JSON formatter supaya konverter mendapat JSON yang bersih dan terbentuk baik. Sisa panduan ini menjelaskan cara membaca keluaran agar Anda bisa memperbaiki kasus-kasus yang tidak bisa disimpulkan sendiri oleh alat.

Cara tipe JSON dipetakan ke Rust

Setiap nilai JSON punya padanannya di Rust, tetapi pemetaannya tidak sepenuhnya satu-lawan-satu:

Nilai JSONTipe Rust
"text"String
42i64 (nilai besar u64, di luar itu f64)
3.14, 2e3f64
true / falsebool
nullOption<T> (atau Option<serde_json::Value>)
[1, 2, 3]Vec<T>
{ ... }struct bernama

Objek adalah tempat sebagian besar pekerjaan berlangsung. Ambil contoh payload REST yang umum:

{
  "id": 101,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "active": true,
  "roles": ["admin", "user"]
}

Konverter menghasilkan struct yang siap-serde:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub id: i64,
    pub name: String,
    pub email: String,
    pub active: bool,
    pub roles: Vec<String>,
}

Setiap skalar dipetakan ke tipe primitif, dan roles menjadi Vec<String>. Satu-satunya tipe JSON yang tidak terpetakan dengan rapi adalah number: JSON hanya punya satu tipe numerik, sedangkan Rust mengharuskan Anda memilih antara i64, u64, dan f64. Pilihan itu mendapat bagiannya sendiri di bawah, karena di situlah sebagian besar generator melakukan kesalahan.

Cara konverter menyimpulkan struct

Empat aturan mencakup hampir semua yang akan Anda berikan padanya: satu struct per bentuk objek, penggabungan kunci-demi-kunci untuk array, penentuan tipe angka yang presisi, dan nama field yang idiomatik.

Inferensi struktural: satu struct bernama per objek

Setiap bentuk objek yang berbeda menjadi struct bernamanya sendiri. Objek bersarang (nested) tidak ditanam inline; konverter mengangkatnya menjadi definisi terpisah lalu merujuknya:

{ "repo": "serde", "owner": { "login": "dtolnay", "id": 100 } }
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub repo: String,
    pub owner: Owner,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Owner {
    pub login: String,
    pub id: i64,
}

owner yang bersarang menjadi struct Owner tersendiri, dirujuk lewat field. Konverter mendeduplikasi bentuk yang identik, sehingga dua field dengan struktur sama berbagi satu struct alih-alih menghasilkan salinan.

Penggabungan array dan field Option

Ketika Anda memberikan array berisi objek, konverter menggabungkannya kunci demi kunci. Kunci yang muncul di sebagian elemen tetapi tidak di elemen lain menjadi Option:

{ "users": [{ "id": 1, "nick": "x" }, { "id": 2 }] }
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub users: Vec<User>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub nick: Option<String>,
}

id muncul di setiap elemen, jadi tetap wajib. nick tidak ada pada pengguna kedua, sehingga menjadi Option<String>. Perhatikan apa yang tidak ada di sini: tidak ada #[serde(default)]. serde sudah memperlakukan Option sebagai opsional dan mendeserialisasi kunci yang hilang menjadi None, jadi atribut itu akan mubazir.

Penentuan tipe angka yang benar: i64, u64, f64

Inilah aturan yang dilewati alat-alat pesaing, dan justru di sinilah yang jebol pada payload nyata. Generator menerapkan tiga tingkatan. Bilangan bulat (integer) dipetakan ke i64. Nilai yang melampaui i64::MAX naik ke u64; di luar u64::MAX, ia jatuh ke f64. Token apa pun yang ditulis dengan titik desimal atau eksponen, seperti 1.0 atau 2e3, menjadi f64 tanpa memandang besarannya.

{ "user_id": 12000000000000000000, "balance": 19.99, "retries": 3 }
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub user_id: u64,
    pub balance: f64,
    pub retries: i64,
}

Di sini user_id lebih besar dari i64::MAX, jadi ia mendarat di u64 dan tetap bisa bolak-balik (round-trip). balance membawa titik desimal, sehingga bertipe f64, bukan bilangan bulat. Ini penting karena serde menegakkan tipe saat deserialisasi. Berikan struct yang mengetikkan ID gaya snowflake atau Twitter sebagai i32, maka nilainya akan meluap (overflow); ketikkan field float sebagai i64, maka serde mengembalikan error alih-alih memotong nilai secara diam-diam. Aturan tiga tingkat inilah yang menjaga rust struct from json yang dihasilkan tetap terdeserialisasi, bukan panik pada ID besar pertama.

Field snake_case dan #[serde(rename)]

Kunci JSON sering kali camelCase; field Rust yang idiomatik memakai snake_case. Konverter mengganti nama field dan menambahkan #[serde(rename)] yang memetakannya kembali ke kunci JSON yang persis:

{ "login": "octocat", "publicRepos": 15, "followerCount": 9001, "createdAt": "2011-01-25" }
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub login: String,
    #[serde(rename = "publicRepos")]
    pub public_repos: i64,
    #[serde(rename = "followerCount")]
    pub follower_count: i64,
    #[serde(rename = "createdAt")]
    pub created_at: String,
}

Kunci yang sudah snake_case, seperti login, tidak diberi rename. Sisanya dipetakan rapi kembali ke format wire, sementara kode Rust Anda tetap terbaca alami.

#[serde(rename)] vs #[serde(rename_all)]

Alat ini mengeluarkan #[serde(rename)] per field karena cara itu selalu berhasil, bahkan ketika satu payload mencampur camelCase, snake_case, dan kunci yang tidak beraturan dalam objek yang sama. Ia tidak pernah perlu menalar konvensi bersama yang mungkin tidak ada.

Ketika setiap field dalam satu struct memang berbagi satu konvensi, Anda bisa meringkas atribut-atribut itu menjadi satu rename di tingkat kontainer. Hapus baris per field dan taruh satu #[serde(rename_all = "camelCase")] pada struct:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
    pub login: String,
    pub public_repos: i64,
    pub follower_count: i64,
    pub created_at: String,
}

Kedua versi mendeserialisasi JSON yang sama. Gunakan rename per field untuk kunci campuran atau tidak beraturan; pakai rename_all ketika struct seragam dan Anda ingin lebih sedikit gangguan visual. serde juga menyediakan konvensi lain seperti "snake_case", "kebab-case", dan "SCREAMING_SNAKE_CASE" untuk atribut yang sama.

Kata kunci Rust dan kunci non-identifier

Kunci JSON hanyalah string, jadi tidak ada yang menghalangi API mengirim kunci bernama type atau match — keduanya kata cadangan (reserved word) di Rust. Generator membersihkan (sanitize) keduanya menjadi identifier yang sah plus rename:

{ "type": "user", "match": true, "first-name": "Ada" }
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    #[serde(rename = "type")]
    pub type_: String,
    #[serde(rename = "match")]
    pub match_: bool,
    #[serde(rename = "first-name")]
    pub first_name: String,
}

Bentuk berakhiran garis bawah (type_) sengaja dipakai ketimbang identifier mentah (raw identifier) seperti r#type. Identifier mentah tidak bisa mengungkapkan setiap kata kunci di setiap posisi; self, crate, dan super justru ditolak sebagai identifier mentah, sehingga pendekatan sanitize-plus-rename adalah satu-satunya yang selalu bisa dikompilasi. Kunci non-identifier seperti first-name atau 2fa yang diawali angka mendapat perlakuan sama: nama Rust yang valid plus rename kembali ke kunci JSON literalnya.

Deserialisasi dengan serde_json

Setelah Anda punya struct-nya, tambahkan dependensi ke Cargo.toml:

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Setelah itu, mem-parse-nya cukup satu baris. Contoh berikut lengkap dan bisa dikompilasi apa adanya:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub id: i64,
    pub name: String,
    pub email: String,
    pub active: bool,
    pub roles: Vec<String>,
}

fn main() -> Result<(), serde_json::Error> {
    let data = r#"{"id":101,"name":"Ada Lovelace","email":"a@ex.com","active":true,"roles":["admin"]}"#;
    let root: Root = serde_json::from_str(data)?;
    println!("{} has {} role(s)", root.name, root.roles.len());
    Ok(())
}

Derive Deserialize melakukan semua pekerjaannya. Gunakan serde_json::from_str untuk string, from_slice untuk byte slice, atau from_reader untuk file atau body HTTP, dan serde_json::to_string untuk menserialisasi nilai kembali menjadi JSON. Inilah hasil dari membuat tipe dengan benar: serde derive yang Anda hasilkan adalah validator runtime, sehingga panggilan deserialize serde_json yang mengembalikan Ok menjadi jaminan bahwa data cocok dengan struct Anda, bukan sekadar keyakinan kompiler.

Tanggal, kunci dinamis, dan field tak dikenal

Beberapa bentuk tidak terpetakan ke field struct biasa, dan generator memberi mereka nilai default yang masuk akal yang kemudian Anda perketat secara manual.

Tanggal. JSON tidak punya tipe tanggal, jadi timestamp ISO atau RFC 3339 datang sebagai String biasa. Untuk penanganan tanggal yang sesungguhnya, ganti field itu menjadi tipe chrono dan aktifkan fitur serde milik chrono (chrono = { version = "0.4", features = ["serde"] }). serde kemudian mem-parse RFC 3339 secara otomatis:

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
    pub name: String,
    pub created_at: DateTime<Utc>,
}

Kunci dinamis. Ketika kunci berubah-ubah saat runtime, misalnya map dari ID ke nilai, struct justru bentuk yang keliru. Gunakan HashMap dengan kunci bertipe String:

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
    pub scores: HashMap<String, i64>,
}

Field ekstra tak dikenal. Untuk mempertahankan struct bertipe namun tetap menangkap kunci yang tidak Anda modelkan, tambahkan field #[serde(flatten)] yang ditopang HashMap. Untuk nilai yang benar-benar dinamis, serde_json::Value adalah penampung serba-guna. Ketika kebutuhan Anda sebenarnya memvalidasi struktur ketimbang menghasilkan tipe, panduan validasi JSON Schema membahas penegakan kontrak dari ujung ke ujung.

json-to-rust vs quicktype vs typeshare vs manual

Tidak ada satu cara terbaik tunggal untuk menghasilkan tipe Rust dari JSON. Semuanya bergantung pada di mana JSON itu berada dan ke arah mana Anda mengonversi.

PendekatanCocok untukCatatan
Konverter online (alat ini, transform.tools)Konversi sekali pakai, payload sensitif, tanpa instalasiMenyimpulkan dari sampel, sepenuhnya di sisi klien
quicktypeKeluaran multi-bahasa, codegen pipelineJuga berbasis sampel; CLI atau web
typeshareBerbagi tipe Rust yang sudah ada dengan TS, Swift, KotlinArah sebaliknya: Rust ke bahasa lain
schemafy / typifyMenghasilkan dari JSON SchemaInput berupa schema, bukan sampel
Menulis struct dengan tanganPayload mungil, belajar serdeKendali penuh, melelahkan dan rawan galat dalam skala besar

Perbedaan yang layak dipahami betul: konverter online dan quicktype sama-sama menyimpulkan tipe dari sampel JSON, sedangkan typeshare berjalan ke arah sebaliknya, mengubah tipe Rust yang beranotasi menjadi TypeScript atau Swift. schemafy dan typify menerima JSON Schema sebagai input, bukan data contoh. Jika basis kode Anda TypeScript ketimbang Rust, pendekatan berbasis sampel yang sama berlaku lewat konverter JSON ke TypeScript, dan panduan interface JSON ke TypeScript membahas sisi itu secara mendalam.

Jebakan umum saat menghasilkan Rust dari JSON

Struct yang dihasilkan adalah titik awal. Waspadai hal-hal berikut sebelum Anda memercayai keluarannya pada data langsung.

  • Satu sampel jarang mengungkap setiap bentuk. Field opsional hanya bisa disimpulkan dari beberapa elemen array. Tempel array yang representatif agar inferensi Option akurat, bukan sekadar tebakan dari satu objek yang kebetulan lengkap.
  • Array kosong atau campuran jatuh kembali ke serde_json::Value. Itu jawaban jujur ketika tidak ada yang bisa disimpulkan. Berikan sampel yang lebih kaya untuk mendapat tipe elemen yang konkret.
  • Jangan mempersempit ID besar menjadi i32. ID gaya snowflake dan sejenisnya melebihi 2^53 dan meluap pada field 32-bit. Pertahankan i64 atau u64 yang dihasilkan.
  • Field yang selalu bernilai null menjadi Option<serde_json::Value>, bukan sekadar opsional biasa. Alat tidak punya tipe untuk disimpulkan, jadi perlakukan sebagai placeholder dan beri tipe sesungguhnya begitu Anda mengetahuinya.
  • serde_json::Value membutuhkan dependensi serde_json. Jika keluaran mengandung Value dan Cargo.toml Anda tidak memuat crate itu, kodenya tidak akan bisa dikompilasi.
  • Tanggal yang dibiarkan sebagai String tidak bisa melakukan perhitungan tanggal. Jika Anda berencana membandingkan atau memformat timestamp, beralihlah ke tipe chrono ketimbang mem-parse string secara manual.

Pertanyaan yang sering diajukan

Mengapa serde menolak JSON saya padahal struct-nya terlihat benar?

Hampir selalu karena ketidakcocokan tipe. Dua penyebab yang lazim adalah nilai floating-point yang mendarat di field i64, dan kunci yang kadang tidak ada tetapi tidak diketikkan sebagai Option. serde menegakkan tipe saat deserialisasi, jadi perbaiki field menjadi f64 atau Option<T>, atau hasilkan ulang struct dari sampel yang representatif.

Tipe angka Rust apa yang seharusnya dipakai untuk integer JSON?

Standarnya i64. Naikkan ke u64 ketika nilai melampaui i64::MAX, dan jatuh kembali ke f64 melewati u64::MAX. Angka apa pun yang ditulis dengan titik desimal atau eksponen dipetakan ke f64 tanpa memandang ukurannya, karena serde menolak float yang dideserialisasi ke field integer.

Bagaimana cara membuat field opsional ketika JSON menghilangkannya?

Ketikkan sebagai Option<T>. serde memperlakukan Option sebagai opsional secara otomatis dan mendeserialisasi kunci yang hilang menjadi None, jadi Anda tidak butuh #[serde(default)]. Konverter menandai field sebagai Option ketika sebagian item yang disampel tidak memiliki kunci itu.

Sebaiknya pakai #[serde(rename)] atau #[serde(rename_all)]?

Gunakan #[serde(rename)] per field ketika payload mencampur gaya penamaan; cara itu selalu berhasil. Jika setiap field dalam satu struct mengikuti satu konvensi, hapus atribut per field dan taruh satu #[serde(rename_all = "camelCase")] pada struct saja. Keduanya mendeserialisasi secara identik.

Bagaimana menangani kunci JSON yang merupakan kata kunci Rust seperti type?

Generator mengeluarkan type_ dengan #[serde(rename = "type")] yang memetakan kembali ke kunci aslinya. Cara itu lebih tangguh dibanding identifier mentah seperti r#type, karena identifier mentah tidak bisa mencakup self, crate, dan super di setiap posisi.

Mengapa tanggal JSON diketikkan sebagai String alih-alih tipe tanggal?

JSON tidak punya tipe tanggal, jadi timestamp hanyalah string di jalur wire, dan String adalah default yang jujur. Untuk penanganan tanggal yang sesungguhnya, ubah field menjadi DateTime<Utc> atau NaiveDate milik chrono dan aktifkan fitur serde milik chrono; serde kemudian mem-parse RFC 3339 untuk Anda.

Bagaimana cara mendeserialisasi JSON ke struct yang dihasilkan?

Tambahkan serde_json ke Cargo.toml, lalu tulis let root: Root = serde_json::from_str(json)?;. Derive Deserialize menangani sisanya. Gunakan from_slice untuk byte slice dan from_reader untuk file atau body HTTP.

Apakah JSON saya tetap privat saat memakai konverter JSON ke struct Rust online?

Ya. Konversi berjalan 100% di browser Anda dengan JavaScript. JSON Anda, termasuk token, ID, dan data pelanggan, tidak pernah meninggalkan halaman dan tidak pernah dikirim ke server. Ketika Anda siap, coba konverter JSON ke Struct Rust pada payload Anda sendiri.

Tag: rust json serde type-safety developer-tools

Artikel Terkait

Lihat semua artikel