JSON naar Rust-struct: serde-structs genereren (gids 2026)
Plak je payload in de JSON naar Rust struct omzetter en kopieer de serde-struct die eruit rolt. Dat is de hele workflow van JSON naar Rust-struct: niets installeren, niets uploaden, alles gebeurt in je browser. Heb je nu meteen een struct nodig, dan ben je binnen enkele seconden klaar.
Een struct genereren en de juiste struct genereren zijn echter twee verschillende problemen. Een omzetter kan alleen maar gokken. En bij Rust staat er meer op het spel. In TypeScript zakt een verkeerde gok terug naar een losse any die je kunt negeren. Bij serde loopt een verkeerde gok vast bij het deserialiseren: een float die in een i64 belandt, een onverwachte null in een niet-optioneel veld, en serde_json::from_str geeft je een Err in plaats van je data. Daarom is het in Rust belangrijker dan in de meeste talen om de struct meteen goed te krijgen.
Hieronder lees je hoe de inferentie precies werkt, welke regel voor numerieke types de meeste omzetters verkeerd doen, wanneer je naar #[serde(rename)] grijpt in plaats van #[serde(rename_all)], en hoe je omgaat met datums, dynamische sleutels en Rust-keywords zodat de output altijd compileert.
Zo zet je JSON om naar Rust
JSON omzetten naar Rust gaat in drie stappen:
- Plak je JSON. Zet een object, array of ruwe API-respons in het invoerveld. De omzetting draait direct en volledig aan de clientkant.
- Stel de output af. Hernoem de root-struct, schakel de serde-derives aan of uit, voeg
DebugenClonetoe, of laat depub-zichtbaarheid weg om bij de stijl van je crate te passen. - Kopieer of download. Pak de gegenereerde Rust met één klik en plak het rechtstreeks in je project.
Dat is het hele transactionele pad. Is je invoer geminificeerd of twijfel je of hij geldig is, haal hem dan eerst door een JSON formatter zodat de omzetter met schone, correct opgebouwde JSON werkt. De rest van deze gids legt uit hoe je de output leest, zodat je de gevallen kunt corrigeren die een tool niet zelf kan afleiden.
Hoe JSON-types op Rust worden afgebeeld
Elke JSON-waarde heeft een Rust-tegenhanger, maar de afbeelding is niet helemaal één-op-één:
| JSON-waarde | Rust-type |
|---|---|
"text" | String |
42 | i64 (grote waarden u64, daarboven f64) |
3.14, 2e3 | f64 |
true / false | bool |
null | Option<T> (of Option<serde_json::Value>) |
[1, 2, 3] | Vec<T> |
{ ... } | een benoemde struct |
Bij objecten zit het echte werk. Neem een typische REST-payload:
{
"id": 101,
"name": "Ada Lovelace",
"email": "ada@example.com",
"active": true,
"roles": ["admin", "user"]
}
De omzetter produceert een serde-klare struct:
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>,
}
Elke scalar wordt afgebeeld op een primitief type, en roles wordt Vec<String>. Het enige JSON-type dat niet netjes past is number: JSON heeft één numeriek type, terwijl Rust je laat kiezen tussen i64, u64 en f64. Die keuze krijgt hieronder een hele sectie, want daar gaan de meeste generatoren de fout in.
Zo leidt de omzetter structs af
Vier regels dekken bijna alles wat je erin stopt: één struct per objectvorm, samenvoegen sleutel voor sleutel bij arrays, precieze numerieke types en idiomatische veldnamen.
Structurele inferentie: één benoemde struct per object
Elke afzonderlijke objectvorm wordt een eigen benoemde struct. Geneste objecten worden niet inline gezet; ze worden opgetild naar losse, gerefereerde definities:
{ "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,
}
De geneste owner wordt een eigen Owner-struct, waar het veld naar verwijst. Identieke vormen worden ontdubbeld, zodat twee velden met dezelfde structuur één struct delen in plaats van kopieën te maken.
Arrays samenvoegen en Option-velden
Geef je een array van objecten door, dan voegt de omzetter ze sleutel voor sleutel samen. Een sleutel die in sommige elementen wel en in andere niet voorkomt, wordt een 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 komt in elk element voor, dus blijft verplicht. nick ontbreekt bij de tweede gebruiker en wordt daarom Option<String>. Let op wat er niet staat: geen #[serde(default)]. serde behandelt Option al als optioneel en deserialiseert een ontbrekende sleutel naar None, dus dat attribuut zou overbodig zijn.
Correcte numerieke types: i64, u64, f64
Dit is de regel die concurrerende tools overslaan, en juist die loopt vast op echte payloads. De generator hanteert drie niveaus. Een integer wordt i64. Overschrijdt een waarde i64::MAX, dan promoveert hij naar u64. Boven u64::MAX valt hij terug op f64. Elk token met een decimaalteken of een exponent, zoals 1.0 of 2e3, wordt f64, ongeacht de grootte.
{ "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,
}
Hier is user_id groter dan i64::MAX, dus belandt hij in u64 en blijft de heen-en-weer-conversie kloppen. balance heeft een decimaalteken en is dus f64, geen integer. serde dwingt dat type namelijk af bij het deserialiseren. Geef het een struct die een snowflake- of Twitter-achtige ID als i32 typeert en de waarde loopt over; typeer een float-veld als i64 en serde geeft een fout in plaats van stilletjes af te kappen. De drie-niveauregel zorgt ervoor dat een gegenereerde rust struct from json blijft deserialiseren in plaats van vast te lopen met een panic op de eerste grote ID.
snake_case-velden en #[serde(rename)]
JSON-sleutels zijn vaak camelCase; idiomatische Rust-velden zijn snake_case. De omzetter hernoemt het veld en voegt een #[serde(rename)] toe die het terugkoppelt aan de exacte JSON-sleutel:
{ "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,
}
Een sleutel die al snake_case is, zoals login, krijgt geen rename. De rest wordt netjes teruggekoppeld aan het wire-formaat, terwijl je Rust-code natuurlijk leest.
#[serde(rename)] vs #[serde(rename_all)]
De tool geeft een #[serde(rename)] per veld omdat dat altijd werkt, ook als één payload camelCase, snake_case en onregelmatige sleutels in hetzelfde object door elkaar gebruikt. De tool hoeft nooit te redeneren over een gedeelde conventie die er misschien niet is.
Delen alle velden in een struct wel één conventie, dan kun je die attributen samenvouwen tot één rename op containerniveau. Verwijder de regels per veld en zet één #[serde(rename_all = "camelCase")] op de 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,
}
Beide versies deserialiseren dezelfde JSON. Gebruik rename per veld voor gemengde of onregelmatige sleutels; grijp naar rename_all wanneer een struct uniform is en je minder ruis wilt. serde levert voor hetzelfde attribuut ook andere conventies, zoals "snake_case", "kebab-case" en "SCREAMING_SNAKE_CASE".
Rust-keywords en sleutels die geen identifier zijn
JSON-sleutels zijn gewoon strings, dus niets houdt een API tegen om een sleutel met de naam type of match te sturen, allebei gereserveerde woorden in Rust. De generator schoont deze op tot een geldige identifier plus een 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,
}
De vorm met een liggend streepje achteraan (type_) is bewust gekozen boven een raw identifier als r#type. Raw identifiers kunnen niet elk keyword op elke plek uitdrukken; met name self, crate en super worden als raw identifier geweigerd, dus een aanpak van opschonen plus rename is de enige die altijd compileert. Sleutels die geen identifier zijn, zoals first-name of 2fa met een cijfer vooraan, krijgen dezelfde behandeling: een geldige Rust-naam en een rename terug naar de letterlijke JSON-sleutel.
Deserialiseren met serde_json
Zodra je de struct hebt, voeg je de dependencies toe aan Cargo.toml:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Daarna heb je maar één regel nodig om het in te lezen. Dit voorbeeld is compleet en compileert zoals het is:
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(())
}
De Deserialize-derive doet al het werk. Gebruik serde_json::from_str voor een string, from_slice voor een byte-slice, of from_reader voor een bestand of HTTP-body, en serde_json::to_string om een waarde terug te serialiseren naar JSON. Dit is de beloning voor het goed kiezen van de types: de serde derive die je genereerde is een runtime-validator, dus een serde_json deserialize-aanroep die Ok teruggeeft, is een garantie dat de data bij je struct paste — niet slechts de overtuiging van een compiler dat het zo was.
Datums, dynamische sleutels en onbekende velden
Sommige vormen passen niet op een gewoon struct-veld, en de generator geeft die een verstandige standaardwaarde die je vervolgens met de hand aanscherpt.
Datums. JSON heeft geen datumtype, dus een ISO- of RFC 3339-timestamp komt binnen als een gewone String. Voor echt werken met datums zet je het veld om naar een chrono-type en schakel je chrono’s serde-feature in (chrono = { version = "0.4", features = ["serde"] }). serde leest RFC 3339 dan automatisch in:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub name: String,
pub created_at: DateTime<Utc>,
}
Dynamische sleutels. Als sleutels tijdens runtime variëren, zoals een map van ID’s naar waarden, is een vaste struct de verkeerde vorm. Gebruik een HashMap met String als sleutel:
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub scores: HashMap<String, i64>,
}
Onbekende extra velden. Wil je een getypeerde struct houden maar toch sleutels opvangen die je niet hebt gemodelleerd, voeg dan een #[serde(flatten)]-veld toe dat op een HashMap steunt. Voor een waarde die echt dynamisch is, is serde_json::Value de vangnetoplossing. Als je eigenlijk structuur wilt valideren in plaats van types te genereren, behandelt de gids over JSON Schema-validatie hoe je een contract van begin tot eind afdwingt.
json-to-rust vs. quicktype vs. typeshare vs. handmatig
Er is geen enkele beste manier om Rust-types uit JSON te produceren. Het hangt af van waar de JSON leeft en in welke richting je omzet.
| Aanpak | Geschikt voor | Opmerking |
|---|---|---|
| Online omzetter (deze tool, transform.tools) | Eenmalige conversies, gevoelige payloads, niets installeren | Leidt af uit een voorbeeld, volledig aan de clientkant |
| quicktype | Output in meerdere talen, codegen in een pipeline | Ook op voorbeelden gebaseerd; CLI of web |
| typeshare | Bestaande Rust-types delen met TS, Swift, Kotlin | Omgekeerde richting: Rust naar andere talen |
| schemafy / typify | Genereren uit een JSON Schema | Invoer is een schema, geen voorbeeld |
| Structs met de hand schrijven | Kleine payloads, serde leren | Volledige controle, bewerkelijk en foutgevoelig op schaal |
Het onderscheid dat je je eigen moet maken: een online omzetter en quicktype leiden beide types af uit een voorbeeld van JSON, terwijl typeshare de andere kant op werkt en geannoteerde Rust-types omzet naar TypeScript of Swift. schemafy en typify nemen een JSON Schema als invoer, geen voorbeelddata. Is je codebase TypeScript in plaats van Rust, dan geldt dezelfde op voorbeelden gebaseerde aanpak met de JSON naar TypeScript Omzetter, en de gids over JSON naar TypeScript-interfaces behandelt die kant grondig.
Veelvoorkomende valkuilen bij het genereren van Rust uit JSON
Gegenereerde structs zijn een startpunt. Let op het volgende voordat je de output op live data vertrouwt.
- Eén voorbeeld onthult zelden elke vorm. Optionele velden kun je alleen afleiden uit meerdere array-elementen. Plak een representatieve array, zodat de
Option-inferentie klopt en geen gok is op basis van één gelukkig object. - Lege of gemengde arrays vallen terug op
serde_json::Value. Dat is het eerlijke antwoord als er niets af te leiden valt. Geef een rijker voorbeeld om een concreet elementtype te krijgen. - Versmal een grote ID niet tot
i32. Snowflake- en soortgelijke ID’s overschrijden 2^53 en lopen over een 32-bits veld. Houd de gegenereerdei64ofu64aan. - Een veld dat altijd
nullis, wordtOption<serde_json::Value>, geen gewone optional. De tool heeft geen type om af te leiden, dus behandel het als een tijdelijke waarde en geef het een echt type zodra je er een weet. serde_json::Valueheeft deserde_json-dependency nodig. Bevat de outputValueen ontbreekt de crate in jeCargo.toml, dan compileert de code niet.- Een datum die als
Stringblijft staan, kan geen datumberekeningen doen. Wil je timestamps vergelijken of formatteren, stap dan over op een chrono-type in plaats van strings met de hand in te lezen.
Veelgestelde vragen
Waarom weigert serde mijn JSON terwijl de struct er goed uitziet?
Bijna altijd een type-mismatch. De twee gebruikelijke oorzaken zijn een floating-pointwaarde die in een i64-veld belandt, en een sleutel die soms ontbreekt maar niet als Option is getypeerd. serde dwingt het type af bij het deserialiseren, dus zet het veld goed op f64 of Option<T>, of genereer de struct opnieuw uit een representatief voorbeeld.
Welk numeriek Rust-type moet een JSON-integer worden?
Standaard i64. Promoveer naar u64 als een waarde i64::MAX overschrijdt, en val terug op f64 voorbij u64::MAX. Elk getal met een decimaalteken of exponent wordt f64, ongeacht de grootte, omdat serde een float weigert die in een integer-veld wordt gedeserialiseerd.
Hoe maak ik een veld optioneel als JSON het weglaat?
Typeer het als Option<T>. serde behandelt Option automatisch als optioneel en deserialiseert een ontbrekende sleutel naar None, dus je hebt #[serde(default)] niet nodig. De omzetter markeert een veld als Option wanneer sommige bemonsterde items de sleutel missen.
Gebruik ik #[serde(rename)] of #[serde(rename_all)]?
Gebruik #[serde(rename)] per veld wanneer een payload naamgevingsstijlen door elkaar gebruikt; dat werkt altijd. Volgt elk veld in een struct één conventie, verwijder dan de attributen per veld en zet in plaats daarvan één #[serde(rename_all = "camelCase")] op de struct. Beide deserialiseren identiek.
Hoe ga ik om met JSON-sleutels die Rust-keywords zijn, zoals type?
De generator geeft type_ met een #[serde(rename = "type")] die terugkoppelt aan de oorspronkelijke sleutel. Dat is robuuster dan een raw identifier als r#type, omdat raw identifiers self, crate en super niet op elke plek kunnen dekken.
Waarom worden JSON-datums als String getypeerd in plaats van als een datumtype?
JSON heeft geen datumtype, dus een timestamp is onderweg gewoon een string, en String is de eerlijke standaard. Voor echt werken met datums verander je het veld in chrono’s DateTime<Utc> of NaiveDate en schakel je chrono’s serde-feature in; serde leest RFC 3339 dan voor je in.
Hoe deserialiseer ik JSON naar de gegenereerde struct?
Voeg serde_json toe aan Cargo.toml en schrijf dan let root: Root = serde_json::from_str(json)?;. De Deserialize-derive doet de rest. Gebruik from_slice voor een byte-slice en from_reader voor een bestand of HTTP-body.
Is mijn JSON privé bij het gebruik van een online JSON naar Rust struct omzetter?
Ja. De omzetting draait 100% in je browser met JavaScript. Je JSON, inclusief tokens, ID’s en klantgegevens, verlaat de pagina nooit en wordt nooit naar een server gestuurd. Als je zover bent, probeer de JSON naar Rust struct omzetter op je eigen payload.