CalcCafe

JSON to Rust

Paste JSON and instantly get idiomatic Rust structs with serde Serialize/Deserialize derives.

Example

Given this JSON:

{"id": 7, "user": {"name": "Ada", "vip": true}}

the tool emits:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
  pub id: i64,
  pub user: User,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
  pub name: String,
  pub vip: bool,
}

How it works

It parses your JSON, infers each field's Rust type (i64, f64, bool, String, Vec, Option, or nested structs), and emits serde-annotated struct definitions with the top-level type named Root.

Good to know

JSON to Rust takes a sample JSON document and generates matching Rust struct definitions decorated with serde's Serialize and Deserialize derives. It's aimed at Rust developers who need to consume or produce an API payload, config file, or fixture and want a typed model to deserialize into without hand-writing every field. The top-level type is always named Root, and each nested object becomes its own named struct.

Reach for it whenever you're wiring up a new HTTP client, writing an integration against a third-party API, or modeling a JSON config — paste a representative response, hit Convert, and drop the output into your crate (you'll need serde and serde_json in Cargo.toml). It also doubles as a quick way to sanity-check what Rust types a given JSON shape implies before committing to a schema.

Reading the output, a few inference rules are worth knowing: whole numbers become i64 and decimals become f64; true/false become bool; strings become String; a null value produces Option<serde_json::Value>; and arrays become Vec<T>. When a key isn't a valid Rust identifier it's rewritten to snake_case with a #[serde(rename = "...")] added, and reserved words like type become raw identifiers such as r#type. For an array of objects, the tool merges every element into one representative struct using the union of all keys it sees.

One practical caveat: type inference is only as good as your sample, so feed it the richest example you have. A field that is null or absent in your sample will be guessed conservatively (often as Option<serde_json::Value> or omitted from the merged struct), and a number that happens to have no decimal point will be typed i64 even if the API can return fractional values. Treat the generated structs as a strong starting point and tighten types — adding Option<T>, swapping i64 for u32, or using a richer type — to match the real contract.

Frequently asked questions

How are JSON keys that aren't valid Rust identifiers handled?
Keys are converted to snake_case field names, and when the result differs from the original a #[serde(rename = "...")] attribute is added so (de)serialization still matches your JSON. Reserved words like type become raw identifiers such as r#type.
How does it type arrays of objects?
It merges all elements of an object array into a single representative struct (taking the union of keys), names it from the singularized field name, and wraps it as Vecso heterogeneous-but-similar items still produce one clean type.
Is my data uploaded anywhere?
No — it runs entirely in your browser. Your input never leaves your device and it works offline once loaded.
Is it free?
Yes, completely free with no sign-up and no limits.

People also ask

What crates do I need to compile the generated Rust structs?
You need serde with its derive feature and serde_json. Add serde = { version = "1", features = ["derive"] } and serde_json = "1" to your Cargo.toml; serde_json is referenced for null and ambiguous values via serde_json::Value.
How do I make a field optional in Rust when JSON keys can be missing?
Wrap the field type in Option, for example pub email: Option<String>. With serde, a missing key deserializes to None by default, and you can add #[serde(default)] for extra safety. The generator only emits Option automatically for null values, so you often add it manually.
Why are all whole numbers generated as i64 instead of u32 or u64?
The tool can't tell a count from a signed value just from JSON, so it picks i64 as a safe default for any integer. If you know a field is always non-negative or has a known range, change it to u32, u64, or i32 yourself.
How does serde rename work for JSON keys that aren't valid Rust names?
serde's #[serde(rename = "originalKey")] tells serde to map a renamed Rust field back to the exact JSON key during serialization and deserialization. This lets you keep idiomatic snake_case field names while still matching camelCase or kebab-case JSON.
What is the difference between JSON to Rust using structs versus serde_json::Value?
serde_json::Value is an untyped, dynamic representation you index at runtime, while generated structs give you compile-time field names and types. Structs are faster and safer for known shapes; Value is better when the structure is unknown or highly variable.
Can I derive other traits like PartialEq or Default on the generated structs?
Yes. The output uses #[derive(Debug, Clone, Serialize, Deserialize)], and you can add traits such as PartialEq, Eq, Hash, or Default to that derive list as long as every field type also implements them.
How does the tool handle an array of objects with slightly different fields?
It merges all elements into a single struct that takes the union of every key seen across the array, then wraps it as Vec<StructName>. Keys present in only some elements may need to be made Option to deserialize the full dataset reliably.
Does converting JSON to Rust send my data to a server?
No. This converter runs entirely in your browser using client-side JavaScript, so the JSON you paste never leaves your device and it continues to work offline after the page has loaded.

Related tools