Example
{
"name": "devtoolsmith",
"version": "1.5.0",
"type": "module",
"maxRetries": 3,
"features": [
"json",
"regex"
],
"database": {
"url": "postgres://localhost/app",
"poolSize": 10,
"timeoutSecs": null
}
}
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub name: String,
pub version: String,
pub r#type: String,
#[serde(rename = "maxRetries")]
pub max_retries: i64,
pub features: Vec<String>,
pub database: Database,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Database {
pub url: String,
#[serde(rename = "poolSize")]
pub pool_size: i64,
#[serde(rename = "timeoutSecs")]
pub timeout_secs: Value,
}
maxRetriesbecamemax_retrieswith#[serde(rename = "maxRetries")].typeis a keyword, so the field isr#type.timeoutSecswasnull, so its type is unknown:Value. Change it toOption<u64>.
If every key in a struct uses the same convention, you can replace the individual renames with one
#[serde(rename_all = "camelCase")] on the struct.
Using the structs
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let text = std::fs::read_to_string("config.json")?;
let config: Config = serde_json::from_str(&text)?;
println!("{} v{}", config.name, config.version);
let pretty = serde_json::to_string_pretty(&config)?;
println!("{pretty}");
Ok(())
}
Type mapping
| JSON | Rust |
|---|---|
| string | String |
| integer | i64 |
| decimal number | f64 |
| true / false | bool |
| object | struct |
| array | Vec<T> |
| sometimes missing or null | Option<T> |
| always null / mixed types | serde_json::Value |
Use u64 for values that are never negative, and &str with a lifetime instead of
String if you need zero-copy parsing. With Skip None when serializing, fields that are
None are left out of the output instead of being written as null.
Writing a Go or TypeScript service too? Use JSON to Go or JSON to TypeScript.
Frequently asked questions
How do I convert JSON to a Rust struct?
Paste a JSON sample into the tool above. It generates a struct for every object with #[derive(Serialize, Deserialize)], converts keys to snake_case fields with #[serde(rename)] where the key differs, and uses Option<T> for values that are null or missing. Add serde and serde_json to Cargo.toml and paste the code into your crate.
What happens when a key is missing from the JSON?
Fields of type Option<T> become None when the key is missing; serde handles this without any attribute. A missing key for any other field type is an error (“missing field”). Add #[serde(default)] to use the type’s Default value instead.
Why is a field called r#type?
type is a Rust keyword. The r# prefix makes it a raw identifier, so it can still be used as a field name, and serde strips the prefix, so it maps to the JSON key "type" without a rename. Keywords that can’t be raw identifiers, like self, get a trailing underscore and a rename instead.
Does serde reject unknown JSON keys?
No, unknown keys are ignored by default, which makes your structs tolerant of new API fields. Add #[serde(deny_unknown_fields)] to a struct to make unknown keys an error, which is useful for config files where a typo should not be ignored.