JSON to Rust serde structs

free

Generate Rust serde-derived structs with snake_case fields and Option<T>.

JSON
385 chars · 385 BValid
Rust
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Root {
    pub id: String,
    pub name: String,
    pub email: String,
    pub age: i64,
    pub bio: serde_json::Value,
    pub address: Address,
    pub tags: Vec<String>,
    #[serde(rename = "createdAt")]
    pub created_at: String,
    pub verified: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Address {
    pub street: String,
    pub city: String,
    pub country: String,
    #[serde(rename = "postalCode")]
    pub postal_code: String,
}
569 chars · 569 B

Generate Rust struct definitions with serde derives from any JSON sample. The converter handles the idioms that make serde happy: snake_case fields with `#[serde(rename = "...")]` when the source key differs, `Option<T>` for optional and nullable fields with `skip_serializing_if = "Option::is_none"` so JSON round-trips cleanly, and `Vec<T>` / `serde_json::Value` for arrays and unknown shapes.

Common use cases

  • Modelling external API responses. Drop the JSON payload, copy the structs into a `types.rs` file, and call `serde_json::from_str` directly. Optional fields with `skip_serializing_if` keep the serialized output clean.
  • Configuration parsing. Pair with `serde_json` / `serde_yaml` / `toml` to parse config files into strongly-typed structs with detailed deserialization errors instead of `Value` access by string keys.
  • Webhook handlers in Axum / Actix. Generate from a sample webhook payload to get a typed `Json<T>` extractor in your handlers — no manual deserialization, automatic 400 responses on malformed input.
  • Bindings for C APIs that return JSON. When wrapping a C library that emits JSON strings, the generated structs give you a clean Rust-side representation without manual parsing.

Frequently asked

Should I derive `Default`?

Useful if you want `T::default()` construction or if you nest the struct inside another that derives `Default`. Note: requires every field type to also implement `Default`, which fails for some serde types.

What does `deny_unknown_fields` do?

When enabled, deserialization fails if the JSON contains keys not declared in the struct. Useful for catching API drift early; harmful if the API adds new fields you don't care about.

Why are date-time strings `String` instead of `chrono::DateTime`?

Stdlib has no datetime type, so the safe default is `String` — no external crate required. To get `chrono::DateTime<Utc>`, swap the field type by hand and add `chrono = { version = "0.4", features = ["serde"] }` to your `Cargo.toml`.

What's `serde_json::Value` for?

Used when the JSON value has too many shapes to model (mixed-type arrays, unknown nested data). It's a catch-all that holds any JSON value at runtime — type-safe but opaque until you destructure.

Why are some field names prefixed with `r#`?

Raw identifier syntax — used when the JSON key is a Rust keyword (`type`, `match`, etc.). The `#[serde(rename)]` attribute keeps the JSON serialization matching the original.

Can I add `Eq` / `Hash` to the derive list?

Not via the toggles yet — Default and PartialEq are exposed. Eq requires every field type to implement Eq, which fails for `f64`. Add by hand to specific structs where appropriate.