JSON to Rust Struct
Generate Rust structs with serde Serialize / Deserialize derives from a JSON sample.
JSON to Rust struct generator
Paste a representative JSON object and this tool generates matching Rust structs with #[derive(Debug, Clone, Serialize, Deserialize)] and serde field rename attributes where the JSON key differs from snake_case. Field names are converted to snake_case, nested objects become their own named structs, and numbers map to i64 or f64 based on the sample value. Pass an array and the first element defines the shape.
Add serde = { features = ["derive"] } to your Cargo.toml to use the generated code. Working in Go instead? Try JSON to Go Struct, or JSON to TypeScript for frontend projects.
Built and maintained by Meet Shah · Last updated
What this tool is used for
- Generating serde-derived structs from a sample response.
- Getting the derive attributes written for you rather than by hand.
- Seeing how nested objects map onto separate structs.
- Getting the rename attributes right for non-Rust key names.
- Producing types strict enough that a schema change fails to compile.
Frequently Asked Questions
- What does serde need to work?
- The derive macros: #[derive(Serialize, Deserialize)] plus the serde crate with the "derive" feature and serde_json. Unlike Go, where JSON support is in the standard library, Rust's is an external crate by design.
- How is naming handled?
- With #[serde(rename_all = "camelCase")] at the struct level, since Rust convention is snake_case fields while most APIs send camelCase. Per-field #[serde(rename = "...")] covers the exceptions.
- How are optional and null fields modelled?
- As Option<T>, which maps cleanly onto JSON null — a genuine advantage over Go, where you need pointers. Add #[serde(default)] so a MISSING field also deserialises rather than erroring.
- What happens to unknown fields?
- They are ignored by default. Add #[serde(deny_unknown_fields)] to reject them, which is worth doing for configuration files where a typo'd key would otherwise be silently discarded.
- How do I handle a field that could be several types?
- With an enum plus #[serde(untagged)], which tries each variant in order. This is where Rust is genuinely more expressive than Go — the type system can represent the union, rather than falling back to interface{}.
Common errors and gotchas
- Accepting inferred `Option`, which one sample cannot determine and serde enforces strictly.
- Missing `#[serde(rename)]` for camelCase keys, which then fail to deserialise silently or loudly.
- Letting an integer become `i32` when the values exceed its range.
- Omitting `#[serde(default)]` where a field may be absent, which makes deserialisation fail.
- Assuming extra keys are ignored, which they are unless you say so explicitly.