What is JSON to Rust Serde Schema Generator?
Rust's type system requires every value to have a known type at compile time. When consuming JSON APIs, that means you need a struct with the exact right field names and types — and Rust's serde crate is the standard way to do it. Serde uses derive macros to generate serialization/deserialization code at compile time, which means zero runtime overhead compared to dynamic parsers. But writing those structs manually for a large API response is slow and error-prone: you need the correct Rust type for every JSON type, proper `#[serde(rename = "...")])` attributes when JSON keys don't match Rust's snake_case convention, and Option<T> wrappers for every nullable field. This generator automates all of that. Paste any JSON object and get compilable Rust code with derive macros, correct types, rename attributes, and optional skip_serializing_if for cleaner re-serialization. The output is compatible with serde_json, the most widely used JSON crate in the Rust ecosystem.
How to Use
- Enter the root struct name — this should match the concept the JSON represents (e.g. "GithubUser", "PaymentIntent")
- Enable "snake_case fields" to add #[serde(rename_all = "camelCase")] at the struct level, converting all camelCase JSON keys to snake_case Rust fields automatically
- Enable "Detect dates" to map ISO-8601 strings to chrono::DateTime<Utc> or chrono::NaiveDate instead of plain String — add chrono to your Cargo.toml if you use this option
- Enable "Option for null" to wrap nullable fields in Option<T> so you can distinguish missing from present-but-empty
- Enable "skip_serializing_if" to add #[serde(skip_serializing_if = "Option::is_none")] on Option fields — the field is omitted from the JSON output when None
- Paste your JSON and click "Generate Rust Structs" — add use serde::{Deserialize, Serialize}; and the serde/serde_json dependencies to your Cargo.toml
Why Use This Tool?
Tips & Best Practices
- Add serde = { version = "1", features = ["derive"] } and serde_json = "1" to your Cargo.toml — the generated code will not compile without these dependencies
- If you use "Detect dates", also add chrono = { version = "0.4", features = ["serde"] } — serde's chrono integration requires the serde feature flag on the chrono crate
- For APIs where a field can be either a string or a number (a common JavaScript anti-pattern), serde_json::Value is the correct type — edit that field manually after generating
- The rename_all = "camelCase" approach works when all JSON keys are consistently camelCase; for mixed-convention APIs, use per-field #[serde(rename = "originalKey")] attributes instead
- When deserializing a large array of objects (e.g. a paginated API list), Rust will infer the item type from the first element — verify the generated Vec<T> type is correct for all items
Frequently Asked Questions
What is serde and why is it the standard for Rust JSON?
Serde is a serialization framework that generates highly optimized serialization code at compile time using Rust macros. Unlike runtime reflection (used by Python's json module or JavaScript's JSON.parse), serde has zero overhead for deserialization — the generated code is as fast as hand-written parsing. It supports not just JSON but also TOML, YAML, MessagePack, Bincode, and dozens of other formats with the same struct definitions.
What is the complete JSON type → Rust type mapping?
JSON string → String (or chrono::DateTime<Utc> / NaiveDate if date detection is on). JSON integer → i64. JSON float → f64. JSON boolean → bool. JSON null → Option<T> where T is inferred from non-null occurrences, or serde_json::Value if always null. JSON array → Vec<T> with T inferred from the first element. JSON object → a named Rust struct. Heterogeneous arrays or dynamic objects → serde_json::Value.
What does rename_all = "camelCase" do exactly?
The #[serde(rename_all = "camelCase")] attribute on a struct tells serde to convert all Rust snake_case field names to camelCase when serializing, and expect camelCase keys when deserializing. So a Rust field named user_id will match the JSON key "userId". This lets you write idiomatic Rust while consuming camelCase REST APIs without per-field rename attributes.
When should I use Option<T> vs a default value?
Use Option<T> when the field can genuinely be absent from the JSON — the consumer of the struct needs to handle both Some and None. Use #[serde(default)] when the field is sometimes missing but should fall back to a default (0, false, empty string, etc.) rather than requiring the caller to unwrap. Mixing both on the same field is valid: #[serde(default)] on an Option<T> field means a missing key deserializes to None rather than an error.
When should I NOT use this generator?
Skip it for payloads with tagged union types (a "type" discriminant field that controls the shape of the rest of the object) — serde's #[serde(tag = "type")] enum feature is better suited. Also skip it when the JSON structure is highly dynamic (arbitrary string keys, unknown nesting depth) — use serde_json::Value or HashMap<String, serde_json::Value> instead of generated structs.
Is my data sent to a server?
No. All struct generation runs entirely in your browser. Your JSON data never leaves your device.
Real-world Examples
Deserializing a Stripe payment intent response
Stripe's API returns camelCase JSON. The generator with "snake_case fields" enabled adds rename_all = "camelCase" at the struct level so all Rust fields are snake_case while matching the API keys. The amount field is an integer (cents), status is a string, and created is a Unix timestamp.
{
"id": "pi_3PqXYZ",
"object": "payment_intent",
"amount": 2000,
"currency": "usd",
"status": "requires_payment_method",
"clientSecret": "pi_3PqXYZ_secret_abc123",
"created": 1721900000,
"livemode": false,
"metadata": {}
}use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub id: String,
pub object: String,
pub amount: i64,
pub currency: String,
pub status: String,
pub client_secret: String,
pub created: i64,
pub livemode: bool,
pub metadata: HashMap<String, serde_json::Value>,
}Handling a nullable nested user profile
When a user has not completed their profile, the API returns null for the profile object. Enabling "Option for null" and "skip_serializing_if" generates Option<Profile> with the skip attribute, so None fields are omitted when re-serializing the struct back to JSON.
{
"userId": "usr_abc",
"username": "rustacean42",
"emailVerified": true,
"profile": null,
"createdAt": "2024-03-01T12:00:00Z"
}use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Profile {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub user_id: String,
pub username: String,
pub email_verified: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<Profile>,
pub created_at: DateTime<Utc>,
}