JSON to Rust
Generate Rust structs with serde Deserialize derives from a JSON sample.
JSON input
Generated code
Enter JSON on the left to generate a type from it.
What is JSON to Rust?
A JSON to Rust converter generates the struct definitions needed to deserialize a JSON payload with serde, the crate essentially every Rust project uses for this. Rust has no dynamic object type to fall back on, so consuming an API means declaring the exact shape up front: a struct per object, a concrete type per field, and a #[derive(Deserialize)] on each one so serde can generate the parsing implementation at compile time. Doing that by hand for a deeply nested response is slow and repetitive, and the compiler will reject the whole thing over a single mismatched type. This generator produces every struct in dependency order with the derive attributes already attached, so you can paste the result into a module and have serde_json::from_str work immediately. Field names are also converted to Rust's idiomatic snake_case automatically, and whenever that differs from the original JSON key — which it will for almost any camelCase payload — a `#[serde(rename = "...")]` attribute is generated on that exact field, so you never have to add renaming attributes by hand.
How to use JSON to Rust
- Paste a representative JSON response into the left panel — the struct fields mirror exactly the keys present in that sample.
- Set a root struct name above the output (defaults to "Root"); each nested object becomes its own struct named after the key holding it.
- Add serde to Cargo.toml with the derive feature (`serde = { version = "1", features = ["derive"] }`) plus serde_json, then paste the structs in.
- Switch the language picker above the output to generate the same shape as a Go struct or TypeScript type instead.
Examples
Flat object with a Deserialize derive
Input
{"id": 1, "name": "Ada", "active": true}
Output
use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Root { pub id: i64, pub name: String, pub active: bool, }
Nested object becomes its own struct
Input
{"user": {"id": 1, "name": "Ada", "roles": ["admin"]}}
Output
use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct User { pub id: i64, pub name: String, pub roles: Vec<String>, } #[derive(Debug, Deserialize)] pub struct Root { pub user: User, }
camelCase key auto-renamed to snake_case
Input
{"userId": 1, "firstName": "Ada"}
Output
use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Root { #[serde(rename = "userId")] pub user_id: i64, #[serde(rename = "firstName")] pub first_name: String, }
Common mistakes
- Forgetting the derive feature on serde in Cargo.toml, which produces a macro resolution error that doesn't obviously point at the missing feature flag.
- Leaving every field non-Option when the API sometimes omits one — serde then fails the whole deserialization at runtime rather than leaving that single field empty.
- Adding your own #[serde(rename_all = "camelCase")] container attribute on top of the generated structs — it's redundant, since each field that needs it already carries its own #[serde(rename = "...")].
- Treating every serde_json::Value field as fine to leave as-is — it compiles, but you still need to match on it by hand to extract a usable value; it's a sign the sample didn't pin down that field's type, not a finished result.
- Marking a field Option<T> only because JSON technically allows null anywhere, rather than because the sample actually showed it missing or null — over-wrapping every field in Option<T> makes a later, genuinely-missing value harder to notice.
Why use this tool
- Emits the #[derive(Debug, Deserialize)] attributes and the serde import, so the output compiles as-is rather than needing boilerplate added.
- Declares nested structs in dependency order, so the file compiles top to bottom without manual reordering.
- Runs entirely client-side, so a response used to generate these structs never leaves your machine before a single line reaches Cargo.
- Converts every field to idiomatic snake_case and attaches a per-field #[serde(rename = "...")] wherever that differs from the source key, so a camelCase payload compiles without touching serde's naming attributes yourself.
- Falls back to serde_json::Value only where a concrete type genuinely can't be pinned down — a null-only sample, or an array mixing an integer and a float for the same key — instead of silently guessing the wrong primitive.