JSON to Rust struct — paste JSON, get serde-derived structs with snake_case fields, serde rename, Option for nullable, Vec for arrays, nested sub-structs.
- Runs locally
- Category Developer & DevOps
- Best for Formatting, validating, shrinking, or inspecting code-adjacent text.
How types, serde renames & Option are inferred
Each JSON object becomes a named struct with #[derive(Serialize, Deserialize)] and snake_case fields. When the Rust field name differs from the wire key (camelCase, kebab-case, PascalCase), a #[serde(rename = "wireKey")] attribute preserves the exact key so (de)serialisation round-trips. Integer numbers infer i64; fractional numbers infer f64; a number that is sometimes whole and sometimes fractional widens to f64. Arrays of objects fold into ONE struct — keys missing from some elements (or seen as null) become Option<T>, the idiomatic Rust nullable. A value that is only ever null types as Option<serde_json::Value>. Empty objects become HashMap<String, serde_json::Value>. Turn on #[serde(default)] so missing keys deserialize to Default instead of erroring; toggle pub fields and the Debug/Clone derives to match your crate style.
What this tool does
Paste any JSON payload — a GitHub API response, a Stripe webhook, a Cargo config, an internal event — and get back a set of Rust `struct` declarations carrying `#[derive(Serialize, Deserialize)]`, ready to drop into a serde-based crate. Wire keys are converted to idiomatic snake_case field names, and whenever the cleaned name differs from the original key (camelCase, kebab-case, PascalCase) a `#[serde(rename = "wireKey")]` attribute preserves the exact key so `serde_json::from_str` and `to_string` round-trip without surprises. Numbers are split, not flattened: an integer sample infers `i64`, a fractional sample infers `f64`, and a field that is sometimes whole and sometimes fractional widens to `f64` so deserialization never fails on a stray decimal. Arrays of objects fold into ONE struct, so `[{"a":1},{"a":1,"b":2}]` gives you a single type with both fields and a `Vec<RootItem>` element — not two near-duplicate structs. Keys that are `null` or missing from some array elements become `Option<T>`, the idiomatic Rust nullable, and empty objects become `HashMap<String, serde_json::Value>`. Reserved keywords like `type` and `match` are escaped as raw identifiers (`r#type`) automatically. Toggle `pub` fields, `#[serde(default)]` on every field, and the Debug/Clone derives to match your crate's style; rename the root struct, copy the output or download the `.rs` file. Everything runs in your browser — the JSON never touches a server.
Tool details
- Input
- Files + Text + Numbers
- The page exposes text boxes, numeric controls, file pickers, or structured inputs depending on the tool.
- Output
- Live result + Copy + Download
- The result area focuses on usable output, with copy, download, or preview actions when supported.
- Privacy
- Browser-side processing
- The main tool logic does not call an external API, so inputs normally stay in the current tab.
- Save / share
- Shareable URL state
- Key settings are encoded in the URL so another person can reopen the same setup.
- Performance budget
- Initial JS <= 25 KB
- No WASM budget is declared, keeping the tool quick to open on mobile.
- Best fit
- Developer & DevOps · Developer
- Category and role tags drive related tools, internal links, and quick fit checks.
How to use
-
1. Input
Paste or drop your content into the tool panel.
-
2. Process
Click the button. All processing is local in your browser.
-
3. Copy / Download
Copy the result or download to disk in one click.
How JSON to Rust Struct fits into your work
Use it in the small gaps between coding, reviewing, debugging, and shipping.
Developer jobs
- Formatting, validating, shrinking, or inspecting code-adjacent text.
- Preparing snippets for documentation, tickets, commits, or handoff.
- Checking a small payload quickly without switching tools.
Developer checks
- Run irreversible transforms like minify or obfuscate on a copy.
- Keep secrets out of pasted snippets unless the tool explicitly stays local.
- Use your normal tests or linter before shipping transformed code.
Good next steps
These links move the current task into a more complete workflow.
- 1 JSON to Go Struct JSON to Go struct — paste JSON, get typed structs with json tags, exported fields, nested sub-structs, pointers for nullable, int64 and omitempty options. Open
- 2 JSON to TypeScript Interface JSON to TypeScript interface — paste JSON, get clean interfaces with union types from arrays, optional vs required detection, root name customizable. Open
- 3 JSON to Java Class JSON to Java POJO, record, or Lombok @Data — paste JSON, get camelCase fields with getters/setters, Jackson @JsonProperty or Gson @SerializedName, nested classes, primitive vs boxed inference. Open
Real-world use cases
Type a third-party API response without hand-writing structs
You're integrating the GitHub API from Rust and the user object has 30+ fields. Curl the endpoint once, paste the JSON here, set the root name to `GitHubUser`, copy the output into `src/github.rs`. The nested `plan` object lifts out as `GitHubUserPlan` automatically, `html_url` keeps its snake_case form, and `serde_json::from_str` fills a fully-typed struct instead of a `serde_json::Value` you have to index with brittle `["plan"]["seats"]` chains.
Map a camelCase API onto idiomatic Rust fields
A JS-fronted backend sends `{ "userId": 7, "isActive": true, "createdAt": "..." }`. Paste it and the fields become `user_id`, `is_active`, `created_at`, each with a `#[serde(rename = "userId")]` attribute. You get clean snake_case Rust that reads naturally and a serde layer that still speaks the API's camelCase on the wire — no manual rename annotations, no clippy `non_snake_case` warnings.
Decide Option vs required for a flaky webhook payload
A vendor's webhook sometimes includes `cancelled_at` and sometimes omits it. Log the first few payloads, paste them as a JSON array, and the optional-field detection wraps `cancelled_at` in `Option<String>`. Now `match event.cancelled_at { Some(ts) => ..., None => ... }` cleanly distinguishes "cancelled" from "still active" instead of panicking on a missing key at deserialize time.
Replace a serde_json::Value blob with a real type
Old code does `let data: serde_json::Value = serde_json::from_str(s)?` then reaches in with `data["user"]["id"].as_i64()` and `.unwrap()`s everywhere. Capture one representative response, paste it, generate the struct, deserialize into it directly. The `.as_i64().unwrap()` chains that panic in prod disappear — `i64` and `f64` are inferred at the field level and the compiler checks every access.
Generate config types for a Rust service
Your service reads a `config.json`. Paste it, name the root `Config`, turn on `#[serde(default)]` so a partial config file still deserializes, and get `Config`, `ConfigServer`, `ConfigServerTls` nested structs with `i64` ports and `bool` flags. Drop them in `config.rs`, call `serde_json::from_reader(file)?`, and your config is type-checked at compile time instead of failing at runtime on a typo'd key.
Common pitfalls
Pasting a single object and expecting some fields to be Option. A single object has no missing-key signal, so every field is required. Option inference only kicks in across an array of objects where some elements carry a key and others don't, or where a value is null. To explore optionality from one sample, wrap it as `[{...}]`.
Expecting u64 / i32 for IDs. JSON gives no sign or width, so every integer infers as i64 — the safe superset. A negative value would break u64, and i32 would overflow on ms timestamps or snowflake IDs. Narrow the type by hand only when you know the real domain; the default always compiles and always deserializes.
Treating a field that's only ever null as a typed field. `{"meta": null}` gives `Option<serde_json::Value>` because null carries no type information. If you know the real shape, feed a non-null sample (`{"meta": {"k": 1}}`) so the tool can infer a proper struct.
Forgetting the serde derive feature. The output uses `#[derive(Serialize, Deserialize)]`, which needs `serde = { version = "1", features = ["derive"] }` in Cargo.toml. Without the `derive` feature the code won't compile — and `serde_json` is required whenever the output contains `serde_json::Value` or a `HashMap` catch-all.
Pasting JSON5 / JSON-with-comments / trailing commas. We use strict `JSON.parse`, so comments, unquoted keys, and trailing commas all error out. Strip them first, or pipe through a JSON formatter as a pre-step.
Privacy
Your JSON never leaves this browser tab. Parsing and type inference use the browser's built-in `JSON.parse`; there is no network call and no analytics on the textarea content. The Share link encodes your input and option choices into the URL so a result is reproducible — that means YOU decide when to share. If the payload is sensitive (internal IDs, customer data, prod responses), copy the generated Rust instead of sharing the link. Only your option booleans (pub / serde default / Debug+Clone) are stored in localStorage so your preferred style persists across visits.
FAQ
Tool combos
Folks in your role tend to reach for these alongside this tool.
- Add Line Numbers Number every line of pasted text — set start, step and separator, zero-pad to align, skip blanks, or strip numbers back off — browser-only
- AES Text Encryptor Encrypt & decrypt text with a password — AES-256-GCM + PBKDF2 via WebCrypto — 100% in your browser, nothing uploaded
- Affine Cipher Encoder & Decoder Encrypt and decrypt the ax+b affine cipher with live modular-inverse check, browser-only
- Age Difference Calculator The exact gap between two birthdays — years/months/days, percentage, and the date one person is twice the other's age — browser-only