JSON to Rust
Generate serde structs from a JSON sample — derives, renames and Option included.
What the generator emits
Every object becomes a pub struct carrying
#[derive(Debug, Clone, Serialize, Deserialize)]. Field names are converted to
snake_case, and any key whose snake_case form differs keeps its wire spelling in
a #[serde(rename = "…")] attribute — so the Rust you read stays idiomatic while
the JSON stays byte-for-byte compatible.
| JSON value | Rust type | Notes |
|---|---|---|
| whole number | i64 | Signed 64-bit covers the range a JSON parser can hand you. |
| decimal number | f64 | — |
true / false | bool | — |
| string | String | Owned, because the payload outlives the parse call. |
| array | Vec<T> | Element types are merged across all elements. |
| object | a named struct | Hoisted to the top level. |
null, or a key missing from a sibling | Option<T> | Both mean "this may not be there". |
| mixed or unknown | serde_json::Value | Import added automatically. |
Optional keys become Option<T>
Rust has no field-level "not present" state, so optionality has to come from
Option. The generator marks a field Option<T> when the key was
null, or when it appeared in one element of an array but not in its siblings:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Address {
pub city: String,
#[serde(rename = "zipCode")]
pub zip_code: Option<String>,
}
Without Option, serde would reject a payload with the key missing. Turn on
Skip serialising absent fields to also add
#[serde(skip_serializing_if = "Option::is_none")], which keeps
null out of your outgoing JSON instead of writing one explicitly.
Using the structs
use serde_json;
let payload: Root = serde_json::from_str(&body)?;
println!("{}", payload.address.city);
// Round-trip back to the wire format
let text = serde_json::to_string(&payload)?; Your Cargo.toml needs serde with the derive feature, plus serde_json for Value:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1" Edge cases worth knowing
- Numbers are not narrowed. A JSON
1becomesi64even if it is obviously a count — narrow types are a decision the generator cannot make for you. - Mixed arrays such as
[1, "two"]fall back toserde_json::Value, since Rust has no untagged union to describe them. - Empty arrays become
Vec<serde_json::Value>— the sample simply contains no element to infer from. - Recursive types are not possible here. A JSON document is a tree, so the generated structs never refer back to themselves and always compile as-is.
FAQ
Do I need use serde::{Deserialize, Serialize}; as well as the derive?
Yes — the derive macro expands to paths under the serde crate, so the traits must be in scope. The generator emits that use line at the top of the file.
Why is the field Option<T> rather than #[serde(default)]?
They solve different problems. #[serde(default)] fills in a value when the key is absent; Option preserves the distinction between absent, null and present. For an API client, keeping that distinction is usually what you want.
Is my JSON uploaded?
No. Everything runs in this browser tab.
Can I change the root struct name?
Yes — edit Root name and every reference updates, including the rename attributes.