1. Home
  2. Developer tools
  3. JSON to Rust Serde Struct

JSON to Rust Serde Struct

Paste a JSON payload and get serde-ready Rust structs, with keyword clashes and renamed keys handled.

Free, no sign-up. Runs in your browser: nothing is uploaded.

How to use JSON to Rust Serde Struct

  1. Paste JSON.
  2. Name the root struct.
  3. Copy the code and add serde to Cargo.toml.

Features

  • #[derive(Serialize, Deserialize)] with Debug, Clone, PartialEq
  • #[serde(rename)] when snake_case differs from the key
  • Keywords such as type become type_
  • Option<T> with skip_serializing_if for optional keys
  • serde_json::Value for mixed types; chrono for dates if chosen

Supported formats

You can open

  • JSON
  • JSON Lines
  • Typed or pasted text

You get

  • RS
  • Copied text

Private by design

Your files are opened and processed by your browser on this device. They are never uploaded to openWorkstand or anyone else, so the JSON to Rust Serde Struct is safe for contracts, IDs, photos and work documents. After you have used it once while online, it also works without an internet connection. How local processing works

Good to know

  • Types are inferred from the samples given; paste several varied samples for accurate optional fields.

Guide

Rust structs for serde from sample JSON

serde is the standard way to read and write JSON in Rust, and it is driven entirely by struct definitions. Deriving Deserialize is easy; working out the right types, names and attributes for a large API response is not. This generator does that part from a sample, producing structs that compile and deserialise the data you pasted.

What you need in Cargo.toml

The structs use serde’s derive macros and, where a field’s type varies, serde_json::Value. Add serde = { version = "1", features = ["derive"] } and serde_json = "1". If you turn on date types, timestamps become DateTime<FixedOffset> from chrono, which needs chrono = { version = "0.4", features = ["serde"] }.

Names, renames and keywords

Rust fields are snake_case. Keys already in snake_case keep their names; others are converted and get #[serde(rename = "…")] so serde still matches the original key — content-type becomes content_type, userName becomes user_name. Keys that are Rust keywords, such as type, match or ref, gain a trailing underscore (type_) plus the rename, which avoids raw identifiers like r#type in your code.

Missing keys and nulls

A key that is missing from some samples becomes Option<T> with #[serde(default, skip_serializing_if = "Option::is_none")]: deserialising works when the key is absent, and serialising leaves it out rather than writing null. A value that is present but sometimes null is also Option<T>, since serde maps JSON null to None.

Collections and loose values

Arrays become Vec<T>, with nested objects turned into their own structs. Fields that were always null or held mixed types become serde_json::Value, which accepts anything; the use serde_json::Value; line is only added when such a field exists.

Using the structs

With serde_json: let user: User = serde_json::from_str(&text)?;. With reqwest’s json feature: let user: User = client.get(url).send()?.json()?; (add .await in async code). Errors name the field and position that failed, which is far easier to debug than a manual parser.

A worked example

From the sample user, the generator writes Address, Item, Order and User. The coupon that only one order has becomes pub coupon: Option<String> with the default and skip attributes, total is f64 because one order has a decimal, and the always-null unit becomes Option<Value>.

Refining the output

Samples cannot reveal everything. A string field that holds a fixed set of values is better as an enum with #[serde(rename_all = "snake_case")]; IDs may deserve a newtype; and a field that was null in every sample needs its real type. Make those changes, then keep a test that deserialises a real response so you notice when the API changes.

Arrays at the root and many samples

When the sample is an array, the root struct describes one element and a comment says so; deserialise into Vec<User>. For newline-delimited JSON, paste every line at once: each one is merged into the same structs, and a key that appears in only some lines becomes an Option, exactly as serde will need it.

Borrowing instead of owning

The generated fields own their data with String and Vec, which is the simplest choice. For very large documents parsed once and discarded, serde can also borrow string slices from the input with lifetimes (&'a str), saving allocations; convert by hand only when profiling shows it matters.

Make Go structs from the same JSON with JSON to Go, frontend types with JSON to TypeScript, Rust reqwest code from a curl example with cURL to Code, or convert a Cargo.toml with the TOML Converter.

Checked against the current version of the JSON to Rust Serde Struct on .

Troubleshooting

The file does not open

Check that it is one of the supported formats above and opens in another app. Damaged files and password-protected PDFs cannot be read until they are repaired or unlocked.

It is slow or stops on a very large file

Everything runs on your device, so speed depends on its memory. Close other tabs, use a computer rather than a phone, or split the file into smaller parts first.

The download did not start

Look for your browser’s download bar or icon. Some browsers ask once before a site may download files; allow it and click the button again.

Frequently asked questions

Could I use rename_all instead of a rename on each field?

Yes, when every key follows one convention: #[serde(rename_all = "camelCase")] on the struct replaces the individual attributes. The generator writes explicit renames because real APIs often mix conventions or include hyphens.

How do I reject unexpected keys?

Add #[serde(deny_unknown_fields)] to the struct. By default serde ignores keys it does not know, which is usually what you want for third-party APIs that add fields over time.

Why i64 and f64 rather than smaller types?

They can hold any number JSON is likely to contain, so deserialising never fails on size. Narrow them to u32 or f32 where the API guarantees smaller values and the memory matters.

Which crates do I need?

serde with the derive feature and serde_json; add chrono with the serde feature if you turned on date types. In Cargo.toml: serde = { version = "1", features = ["derive"] } and serde_json = "1".

Why is a field Option when it is never null?

Because at least one sample did not contain that key. Without Option, deserialising a document that lacks it fails with “missing field”.

Keep working with the result

openWorkstand also includes a free workspace with documents, spreadsheets, tasks, projects and a calendar that save to a folder on your computer.