JSON’dan Rust struct’a: serde struct oluşturma (2026 rehberi)
Payload’ınızı JSON’dan Rust dönüştürücüye yapıştırın ve ürettiği serde struct’ını kopyalayın. JSON’dan Rust struct’a iş akışının tamamı bu kadardır: kurulacak bir şey yok, hiçbir şey yüklenmez, her şey tarayıcınızda yapılır. “Hemen şimdi bir struct lazım” durumunu saniyeler içinde çözer.
Yine de bir struct üretmek ile doğru struct’ı üretmek farklı problemlerdir. Bir dönüştürücü yalnızca tahmin yürütebilir. Rust ise tam burada bahsi yükseltir. TypeScript’te yanlış bir tahmin, görmezden gelebileceğiniz gevşek bir any’ye düşer. serde ile ise yanlış bir tahmin, deserialize sırasında başarısız olur: bir i64 içine düşmüş bir kayan nokta değeri, isteğe bağlı olmayan bir alanda beklenmedik bir null — ve serde_json::from_str size verinizi değil bir Err verir. Struct’ı doğru yapmanın Rust’ta çoğu dilden daha fazla önem taşımasının nedeni budur.
Aşağıda çıkarımın gerçekte nasıl işlediğini, çoğu dönüştürücünün yanlış yaptığı sayı tipleme kuralını, ne zaman #[serde(rename)]’e ne zaman #[serde(rename_all)]’a başvuracağınızı ve çıktının her zaman derlenmesi için tarihleri, dinamik anahtarları ve Rust anahtar sözcüklerini nasıl ele alacağınızı bulacaksınız.
JSON nasıl Rust’a dönüştürülür
JSON’u Rust’a dönüştürmek üç adım sürer:
- JSON’unuzu yapıştırın. Bir nesneyi, diziyi veya ham API yanıtını giriş kutusuna bırakın. Dönüştürme anında ve tümüyle istemci tarafında çalışır.
- Çıktıyı ayarlayın. Kök struct’ı yeniden adlandırın, serde derive’larını açıp kapatın,
DebugveCloneekleyin veya crate’inizin stiline uyması içinpubgörünürlüğünü kaldırın. - Kopyalayın veya indirin. Üretilen Rust’ı tek tıkla alın ve doğrudan projenize yapıştırın.
İşlemsel yol bundan ibaret. Girdiniz küçültülmüşse veya geçerli olduğundan emin değilseniz, önce onu bir JSON biçimlendiriciden geçirin ki dönüştürücünün üzerinde çalışacağı temiz, iyi biçimlendirilmiş bir JSON olsun. Bu rehberin geri kalanı, bir aracın kendi başına çıkaramadığı durumları düzeltebilmeniz için çıktıyı nasıl okuyacağınızı anlatır.
JSON tipleri Rust’a nasıl eşlenir
Her JSON değerinin bir Rust karşılığı vardır, ama eşleme tam olarak birebir değildir:
| JSON değeri | Rust tipi |
|---|---|
"text" | String |
42 | i64 (büyük değerler u64, ötesinde f64) |
3.14, 2e3 | f64 |
true / false | bool |
null | Option<T> (veya Option<serde_json::Value>) |
[1, 2, 3] | Vec<T> |
{ ... } | adlandırılmış bir struct |
İşin asıl yapıldığı yer nesnelerdir. Tipik bir REST payload’ını ele alalım:
{
"id": 101,
"name": "Ada Lovelace",
"email": "ada@example.com",
"active": true,
"roles": ["admin", "user"]
}
Dönüştürücü, serde’ye hazır bir struct üretir:
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>,
}
Her skaler bir ilkel tipe eşlenir ve roles, Vec<String> olur. Temiz biçimde eşlenmeyen tek JSON tipi number’dır: JSON’un tek bir sayısal tipi vardır, oysa Rust sizi i64, u64 ve f64 arasında seçim yapmaya zorlar. Bu seçim aşağıda ayrı bir bölümün konusudur, çünkü çoğu üreticinin yanlış yaptığı yer tam da burasıdır.
Dönüştürücü struct’ları nasıl çıkarır
Ona vereceğiniz hemen her şeyi dört kural kapsar: her nesne şekli için bir struct, diziler için anahtar anahtar birleştirme, hassas sayı tiplemesi ve deyimsel alan adları.
Yapısal çıkarım: her nesne için adlandırılmış bir struct
Birbirinden farklı her nesne şekli, kendi adlandırılmış struct’ı olur. İç içe nesneler satır içine alınmaz; ayrı, referans verilen tanımlara yükseltilir:
{ "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,
}
İç içe geçmiş owner, alan üzerinden referans verilen kendi Owner struct’ı olur. Aynı şekiller yinelenmez; bu nedenle aynı yapıya sahip iki alan, kopyalar üretmek yerine tek bir struct’ı paylaşır.
Dizi birleştirme ve Option alanları
Bir nesne dizisi verdiğinizde, dönüştürücü onları anahtar anahtar birleştirir. Bazı öğelerde bulunup diğerlerinde bulunmayan bir anahtar Option olur:
{ "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 her öğede yer alır, bu yüzden zorunlu kalır. nick, ikinci kullanıcıda eksiktir, bu yüzden Option<String> olur. Burada olmayan şeye dikkat edin: #[serde(default)] yok. serde zaten Option’ı isteğe bağlı olarak görür ve eksik bir anahtarı None olarak deserialize eder; dolayısıyla bu öznitelik gereksiz olurdu.
Doğru sayı tiplemesi: i64, u64, f64
Rakip araçların atladığı kural budur ve gerçek payload’larda tökezleyen de odur. Üretici üç katman uygular. Bir tam sayı i64’e eşlenir. Bir değer i64::MAX’ı aşarsa u64’e yükseltilir. u64::MAX’ın ötesinde f64’e geri düşer. 1.0 veya 2e3 gibi ondalık nokta ya da üs ile yazılmış herhangi bir belirteç, büyüklüğünden bağımsız olarak f64’e eşlenir.
{ "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,
}
Burada user_id, i64::MAX’tan büyüktür, bu yüzden u64’e yerleşir ve yine de gidip geri döner. balance bir ondalık nokta taşır, bu yüzden tam sayı değil f64’tür. Bu önemlidir, çünkü serde tipi deserialize sırasında zorunlu kılar. Ona snowflake veya Twitter tarzı bir kimliği i32 olarak tipleyen bir struct verin, değer taşar; bir kayan nokta alanını i64 olarak tipleyin, serde sessizce kırpmak yerine bir hata döndürür. Üretilen bir rust struct from json’ın ilk büyük kimlikte panik yapmak yerine deserialize etmeye devam etmesini sağlayan şey, bu üç katmanlı kuraldır.
snake_case alanları ve #[serde(rename)]
JSON anahtarları sıklıkla camelCase’tir; deyimsel Rust alanları ise snake_case’tir. Dönüştürücü alanı yeniden adlandırır ve onu tam JSON anahtarına geri eşleyen bir #[serde(rename)] ekler:
{ "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,
}
login gibi zaten snake_case olan bir anahtar, yeniden adlandırma almaz. Diğerleri ise, Rust kodunuz doğal biçimde okunurken aktarım biçimine (wire format) temiz bir şekilde geri eşlenir.
#[serde(rename)] vs #[serde(rename_all)]
Araç, alan başına bir #[serde(rename)] üretir çünkü bu her zaman işe yarar — tek bir payload aynı nesnede camelCase, snake_case ve düzensiz anahtarları karıştırsa bile. Var olmayabilecek ortak bir kural üzerine akıl yürütmek zorunda hiç kalmaz.
Bir struct’taki her alan tek bir kuralı gerçekten paylaşıyorsa, bu öznitelikleri tek bir kapsayıcı düzeyindeki yeniden adlandırmada toplayabilirsiniz. Alan başına satırları silin ve struct’a tek bir #[serde(rename_all = "camelCase")] koyun:
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,
}
Her iki sürüm de aynı JSON’u deserialize eder. Karışık veya düzensiz anahtarlar için alan başına rename kullanın; bir struct tek biçimliyse ve daha az gürültü istiyorsanız rename_all’a başvurun. serde ayrıca aynı öznitelik için "snake_case", "kebab-case" ve "SCREAMING_SNAKE_CASE" gibi başka kurallar da sunar.
Rust anahtar sözcükleri ve tanımlayıcı olmayan anahtarlar
JSON anahtarları yalnızca karakter dizileridir, bu yüzden bir API’nin type veya match adlı bir anahtar göndermesini hiçbir şey engellemez — ki bunların ikisi de Rust’ta ayrılmış sözcüklerdir. Üretici bunları geçerli bir tanımlayıcıya ve bir yeniden adlandırmaya dönüştürerek temizler:
{ "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,
}
Sondaki alt çizgi biçimi (type_), r#type gibi ham bir tanımlayıcıya karşı bilinçli olarak tercih edilir. Ham tanımlayıcılar her sözcüğü her konumda ifade edemez; özellikle self, crate ve super ham tanımlayıcı olarak reddedilir, bu yüzden her zaman derlenen tek yaklaşım temizle-ve-yeniden-adlandır yaklaşımıdır. first-name ya da başında rakam olan 2fa gibi tanımlayıcı olmayan anahtarlar da aynı işleme tabidir: geçerli bir Rust adı ve literal JSON anahtarına geri bir yeniden adlandırma.
serde_json ile deserialize etme
Struct’ı elde ettikten sonra bağımlılıkları Cargo.toml’a ekleyin:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Ardından ayrıştırmak tek satırdır. Bu örnek eksiksizdir ve olduğu gibi derlenir:
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(())
}
Bütün işi Deserialize derive’ı yapar. Bir karakter dizisi için serde_json::from_str, bir bayt dilimi için from_slice veya bir dosya ya da HTTP gövdesi için from_reader kullanın; bir değeri tekrar JSON’a serialize etmek için ise serde_json::to_string kullanın. Tipleri doğru yapmanın getirisi budur: ürettiğiniz serde derive, bir çalışma zamanı doğrulayıcısıdır; dolayısıyla Ok döndüren bir serde_json deserialize çağrısı, derleyicinin verinin struct’ınıza uyduğuna dair bir inancı değil, gerçekten uyduğunun bir garantisidir.
Tarihler, dinamik anahtarlar ve bilinmeyen alanlar
Bazı şekiller düz bir struct alanına eşlenmez; üretici bunlara, sonradan elle sıkılaştıracağınız makul bir varsayılan verir.
Tarihler. JSON’un tarih tipi yoktur, bu yüzden bir ISO veya RFC 3339 zaman damgası düz bir String olarak gelir. Gerçek tarih işlemleri için alanı bir chrono tipine geçirin ve chrono’nun serde özelliğini etkinleştirin (chrono = { version = "0.4", features = ["serde"] }). serde ardından RFC 3339’u otomatik olarak ayrıştırır:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub name: String,
pub created_at: DateTime<Utc>,
}
Dinamik anahtarlar. Anahtarlar çalışma zamanında değiştiğinde — kimliklerden değerlere bir eşlem gibi — sabit bir struct yanlış şekildir. String ile anahtarlanan bir HashMap kullanın:
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub scores: HashMap<String, i64>,
}
Bilinmeyen fazladan alanlar. Tipli bir struct’ı korurken modellemediğiniz anahtarları yine de yakalamak için, bir HashMap ile desteklenen bir #[serde(flatten)] alanı ekleyin. Gerçekten dinamik olan bir değer için ise serde_json::Value her şeyi toplayan seçenektir. Asıl ihtiyacınız tip üretmek değil de yapıyı doğrulamaksa, JSON Schema doğrulama rehberi bir sözleşmeyi baştan sona zorunlu kılmayı anlatır.
json-to-rust vs quicktype vs typeshare vs elle yazma
JSON’dan Rust tipleri üretmenin tek bir en iyi yolu yoktur. Bu, JSON’un nerede bulunduğuna ve hangi yönde dönüştürdüğünüze bağlıdır.
| Yaklaşım | En uygun olduğu durum | Not |
|---|---|---|
| Çevrimiçi dönüştürücü (bu araç, transform.tools) | Tek seferlik dönüştürmeler, hassas payload’lar, sıfır kurulum | Bir örnekten çıkarım yapar, tümüyle istemci tarafında |
| quicktype | Çok dilli çıktı, pipeline kod üretimi | O da örneğe dayalı; CLI veya web |
| typeshare | Mevcut Rust tiplerini TS, Swift, Kotlin ile paylaşma | Ters yön: Rust’tan diğer dillere |
| schemafy / typify | Bir JSON Schema’dan üretme | Girdi bir örnek değil, bir şemadır |
| Struct’ları elle yazma | Küçük payload’lar, serde öğrenme | Tam kontrol; ölçekte yorucu ve hataya açık |
Akılda tutmaya değer ayrım şudur: bir çevrimiçi dönüştürücü ve quicktype’ın ikisi de tipleri bir JSON örneğinden çıkarır; typeshare ise ters yönde çalışıp açıklama eklenmiş Rust tiplerini TypeScript veya Swift’e çevirir. schemafy ve typify, girdi olarak örnek veriyi değil bir JSON Schema’yı alır. Kod tabanınız Rust değil de TypeScript ise, aynı örneğe dayalı yaklaşım JSON’dan TypeScript’e dönüştürücü ile geçerlidir ve JSON’dan TypeScript’e arayüz rehberi o tarafı derinlemesine ele alır.
JSON’dan Rust üretirken sık karşılaşılan tuzaklar
Üretilen struct’lar bir başlangıç noktasıdır. Çıktıya canlı veride güvenmeden önce şunlara dikkat edin.
- Tek bir örnek her şekli nadiren ortaya koyar. İsteğe bağlı alanlar yalnızca birden fazla dizi öğesinden çıkarılabilir.
Optionçıkarımının şanslı tek bir nesneden yapılan bir tahmin olması yerine doğru olması için temsili bir dizi yapıştırın. - Boş veya karışık diziler
serde_json::Value’ye geri düşer. Çıkarım yapılacak bir şey olmadığında dürüst yanıt budur. Somut bir öğe tipi elde etmek için daha zengin bir örnek verin. - Büyük bir kimliği
i32’ye daraltmayın. Snowflake ve benzeri kimlikler 2^53’ü aşar ve 32 bitlik bir alanı taşırır. Üretileni64veyau64’ü koruyun. - Her zaman
nullolan bir alan, düz bir isteğe bağlı değilOption<serde_json::Value>olur. Aracın çıkaracağı bir tip yoktur; bu yüzden onu bir yer tutucu olarak görün ve bir tip öğrendiğinizde ona gerçek bir tip verin. serde_json::Value,serde_jsonbağımlılığını gerektirir. ÇıktıValueiçeriyorsa veCargo.toml’unuzda crate eksikse kod derlenmez.Stringolarak bırakılan bir tarih, tarih hesabı yapmaz. Zaman damgalarını karşılaştırmayı veya biçimlendirmeyi planlıyorsanız, karakter dizilerini elle ayrıştırmak yerine bir chrono tipine geçin.
Sıkça sorulan sorular
serde, struct doğru görünse bile JSON’umu neden reddediyor?
Neredeyse her zaman bir tip uyuşmazlığı. İki olağan neden şudur: bir kayan nokta değerinin bir i64 alanına düşmesi ve bazen bulunmayan ama Option olarak tiplenmemiş bir anahtar. serde tipi deserialize sırasında zorunlu kılar; bu yüzden alanı f64 veya Option<T> olarak düzeltin ya da struct’ı temsili bir örnekten yeniden üretin.
Bir JSON tam sayısı hangi Rust sayı tipine dönüşmeli?
Varsayılan olarak i64 kullanın. Bir değer i64::MAX’ı aştığında u64’e yükseltin ve u64::MAX’ın ötesinde f64’e geri düşün. Ondalık nokta veya üs ile yazılmış herhangi bir sayı, boyutundan bağımsız olarak f64’e eşlenir, çünkü serde bir tam sayı alanına deserialize edilen bir kayan noktayı reddeder.
JSON bir alanı atladığında onu nasıl isteğe bağlı yaparım?
Onu Option<T> olarak tipleyin. serde Option’ı otomatik olarak isteğe bağlı sayar ve eksik bir anahtarı None olarak deserialize eder; bu yüzden #[serde(default)]’a ihtiyacınız yoktur. Dönüştürücü, örneklenen bazı öğelerde anahtar eksik olduğunda bir alanı Option olarak işaretler.
#[serde(rename)] mi yoksa #[serde(rename_all)] mı kullanmalıyım?
Bir payload adlandırma stillerini karıştırdığında alan başına #[serde(rename)] kullanın; bu her zaman işe yarar. Bir struct’taki her alan tek bir kurala uyuyorsa, alan başına öznitelikleri silin ve bunun yerine struct’a tek bir #[serde(rename_all = "camelCase")] koyun. İkisi de aynı şekilde deserialize eder.
type gibi Rust anahtar sözcüğü olan JSON anahtarlarını nasıl ele alırım?
Üretici, orijinal anahtara geri eşleyen bir #[serde(rename = "type")] ile birlikte type_ üretir. Bu, r#type gibi ham bir tanımlayıcıdan daha sağlamdır, çünkü ham tanımlayıcılar self, crate ve super’i her konumda kapsayamaz.
JSON tarihleri neden bir tarih tipi yerine String olarak tipleniyor?
JSON’un tarih tipi yoktur, bu yüzden bir zaman damgası aktarımda yalnızca bir karakter dizisidir ve String dürüst varsayılandır. Gerçek tarih işlemleri için alanı chrono’nun DateTime<Utc> veya NaiveDate tipine değiştirin ve chrono’nun serde özelliğini etkinleştirin; serde ardından RFC 3339’u sizin için ayrıştırır.
JSON’u üretilen struct’a nasıl deserialize ederim?
serde_json’u Cargo.toml’a ekleyin, ardından let root: Root = serde_json::from_str(json)?; yazın. Gerisini Deserialize derive’ı halleder. Bir bayt dilimi için from_slice, bir dosya veya HTTP gövdesi için from_reader kullanın.
Çevrimiçi bir JSON’dan Rust struct dönüştürücü kullanırken JSON’um gizli kalır mı?
Evet. Dönüştürme %100 tarayıcınızda JavaScript ile çalışır. Token’lar, kimlikler ve müşteri verileri dahil olmak üzere JSON’unuz sayfadan asla ayrılmaz ve hiçbir zaman bir sunucuya gönderilmez. Hazır olduğunuzda, kendi payload’ınız üzerinde JSON’dan Rust dönüştürücüyü deneyin.