DevToolsmith

JSON to Rust Struct Converter

Paste JSON to generate Rust structs with serde derives. Keys become snake_case fields with #[serde(rename)] where needed, nullable or missing values become Option<T>, and Rust keywords are handled.

Runs 100% in your browser — nothing is uploaded

JSON
Tip: drop a file here361 chars · 361 bytes · 14 lines
Rust structs
766 chars · 766 bytes · 33 lines

Tip: paste an array of several objects — keys missing from some of them become optional (Option<T>).

Example

JSON input
{
  "name": "devtoolsmith",
  "version": "1.5.0",
  "type": "module",
  "maxRetries": 3,
  "features": [
    "json",
    "regex"
  ],
  "database": {
    "url": "postgres://localhost/app",
    "poolSize": 10,
    "timeoutSecs": null
  }
}
Generated Rust (root name: Config)
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,
}
  • maxRetries became max_retries with #[serde(rename = "maxRetries")].
  • type is a keyword, so the field is r#type.
  • timeoutSecs was null, so its type is unknown: Value. Change it to Option<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

Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
main.rs
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

JSONRust
stringString
integeri64
decimal numberf64
true / falsebool
objectstruct
arrayVec<T>
sometimes missing or nullOption<T>
always null / mixed typesserde_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.