JSON to Rust Struct Converter

Paste JSON and get serde-ready Rust structs for serde_json, reqwest or axum. The sample stays in your browser while the code is generated.

JSON → Rust

Input

Settings

History

Load from URL

serde types from a JSON sample

In Rust, typed deserialization with serde is the norm: you declare structs, derive Deserialize, and serde_json::from_str does the rest. The tedious part is writing the structs for a nested API response with exactly the right field names and option types. This generator infers them from a sample payload and writes idiomatic code with the attributes serde needs.

Mapping rules

  • Every object becomes a pub struct with pub fields and #[derive(Debug, Clone, Serialize, Deserialize)].
  • Whole numbers become i64 and other numbers f64; strings, booleans and arrays become String, bool and Vec<T>.
  • Field names are snake_case. When the JSON uses another convention consistently, a single container attribute such as #[serde(rename_all = "camelCase")] or "kebab-case" maps them; individual keys that do not fit get #[serde(rename = "...")].
  • Rust keywords are avoided: a type key becomes a field like root_type with a rename attribute.
  • Objects inside an array are merged. A field that is missing or null in part of the sample becomes Option<T>, which serde treats as None when the key is absent.
  • A field that is only ever null becomes Option<serde_json::Value>.
  • Arrays that mix types become an #[serde(untagged)] enum with one variant per type, which serde tries in order.
  • A JSON array at the root becomes pub type Root = Vec<RootElement>;.

A comment at the top lists the crates required — serde with the derive feature, and serde_json — and shows let data: Root = serde_json::from_str(&json)?;.

Root type name

Root type name (default Root) is the only option. It names the top-level struct or alias and is converted to PascalCase, so github repo becomes GithubRepo.

Adjusting the result

The derives include Debug and Clone for convenience; trim them if you care about compile times. Inference cannot know more than the sample shows: timestamps stay String (use chrono or time with their serde features if needed), small integer ranges are not narrowed to u8 or i32, and values above 2^53 trigger a warning — switch those to u64 or i128. If an API may add fields, serde ignores unknown keys by default, so the structs keep working. Everything is computed client-side; use the Rust formatter after editing.

With reqwest, the structs plug straight into response.json::<Root>().await?; with axum they work as Json<Root> extractors. If you only need part of a large response, delete the fields you do not use — serde skips keys that have no matching field, so a trimmed struct still deserializes.

Examples

GitHub-style repository object

Produces Repository and Owner structs; license becomes Option<serde_json::Value> and the type key is renamed to avoid the keyword.

Input
{
  "id": 1296269,
  "full_name": "octocat/Hello-World",
  "private": false,
  "stargazers_count": 80,
  "license": null,
  "owner": { "login": "octocat", "id": 1, "type": "User" },
  "topics": ["octocat", "api"]
}
Output
// Requires serde (with the "derive" feature) and serde_json:
//   let data: Repository = serde_json::from_str(&json)?;

use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Repository {
    pub id: i64,
    pub full_name: String,
    pub private: bool,
    pub stargazers_count: i64,
    pub license: Option<serde_json::Value>,
    pub owner: Owner,
    pub topics: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Owner {
    pub login: String,
    pub id: i64,
    #[serde(rename = "type")]
    pub owner_type: String,
}
Open this example in the tool

camelCase API with optional fields

A rename_all attribute maps the camelCase keys, couponCode becomes Option<String>, and the root is a Vec type alias.

Input
[
  { "orderId": "ord_1", "totalAmount": 129.9, "isPaid": true },
  { "orderId": "ord_2", "totalAmount": 79.5, "isPaid": false, "couponCode": "SPRING10" }
]
Output
// Requires serde (with the "derive" feature) and serde_json:
//   let data: Root = serde_json::from_str(&json)?;

use serde::{Serialize, Deserialize};

pub type Root = Vec<RootElement>;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RootElement {
    pub order_id: String,
    pub total_amount: f64,
    pub is_paid: bool,
    pub coupon_code: Option<String>,
}
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
Unexpected content after the end of the JSON value
Explained
Several JSON values were pasted, for example lines from a JSON Lines file.Convert the lines to an array with JSON Lines to JSON first.
Missing comma after line 1
Explained
A comma is missing between two properties in the sample.Add the comma at the reported position.
Some integers are larger than 2^53; they are typed as floating-point numbers. Use a 64-bit or big-integer type if you need exact values.
Explained
A warning: the sample has integers too large to infer exactly, such as snowflake IDs.Change the field to u64, i128 or String.

Frequently asked questions

Which crates do I need?

serde with the derive feature, and serde_json for parsing. Add them with cargo add serde --features derive and cargo add serde_json.

How are optional fields represented?

As Option<T>. serde fills them with None when the key is missing or the value is null.

What is #[serde(untagged)] for?

It lets an enum match a JSON value by shape rather than by a tag, which is how a mixed array such as [1, “a”] is decoded.

Is my JSON uploaded?

No, generation is done locally in your browser.

Related tools