JSON to Rust Struct

Generate a Rust struct with serde derive attributes from a JSON document.

JSON Input
Rust Struct Output
Rust Struct output appears here

Related Tools

Documentation

What is JSON to Rust?

This tool converts a JSON sample into Rust structs derived for serde — the de facto standard (de)serialization framework in Rust. Each generated struct derives Debug, Serialize, Deserialize, so it works directly with serde_json::from_str / to_string with no extra glue code.

How it works

Nested JSON objects become their own pub struct, printed child-first. Field names are converted to snake_case, the Rust convention. When that conversion produces a name different from the raw JSON key — isActive becoming is_active — a #[serde(rename = "isActive")] attribute is added above that field so serde still maps to the original wire key. A field whose sample value was null is wrapped in Option<T>. Numbers become i64 or f64 — the generator always picks the widest safe default rather than guessing a smaller type like u32 or f32. A field name that's a Rust keyword (like type or match) is escaped as a raw identifier with r#.

Features

  • #[derive(Debug, Serialize, Deserialize)] on every generated struct, ready for serde_json
  • Automatic snake_case field renaming with a matching #[serde(rename = "...")] so the JSON wire format is untouched
  • Nullable sample values become Option<T>, matching how serde represents absent/null JSON
  • Rust keyword field names are escaped with r# raw identifiers instead of producing invalid code
  • Struct name collisions with String, Vec, Option, or Box are renamed with a Data suffix

Example

Input: { "id": 101, "username": "alice_dev", "isActive": true, "signupBonus": null, "address": { "city": "Berlin", "zipCode": "10115" }, "tags": ["admin", "beta"] }

Output:

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct Address {
    pub city: String,
    #[serde(rename = "zipCode")]
    pub zip_code: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Root {
    pub id: i64,
    pub username: String,
    #[serde(rename = "isActive")]
    pub is_active: bool,
    #[serde(rename = "signupBonus")]
    pub signup_bonus: Option<serde_json::Value>,
    pub address: Address,
    pub tags: Vec<String>,
}

Common errors

A null sample value produces Option<serde_json::Value> — untyped, since there's nothing concrete to infer. Narrow it to a real type once you know the field's actual shape, or the compiler won't help you catch misuse. serde_json::Value requires the serde_json crate as a dependency even when a field's type is otherwise fully typed. An empty sample array has no element to infer from and falls back to Vec<serde_json::Value>.

Best practices

Add #[serde(rename_all = "camelCase")] at the struct level instead of per-field rename attributes if every field in that struct needs the same camelCase-to-snake_case mapping — it's more concise for structs with many renamed fields. Narrow the numeric types (i32, u32, f32) once you know your actual value ranges — the generator defaults to the widest safe types, which cost more memory than necessary for large collections of structs.

Frequently Asked Questions

Why serde specifically?

serde with serde_json is the de facto standard for JSON (de)serialization in Rust — the generated struct derives Serialize and Deserialize so it works directly with serde_json::from_str / to_string with no extra glue code.

Why are field names converted to snake_case?

Rust convention is snake_case struct fields. When a JSON key isn't already snake_case (e.g. camelCase from a JS-authored API), the generator adds a #[serde(rename = "...")] attribute so serde still maps to the original JSON key on the wire while your Rust code reads idiomatically.

How are optional/nullable fields handled?

A field whose sample value is null is wrapped in Option, matching how serde represents an absent or null JSON value in Rust.

What type do numbers get?

Whole numbers become i64, numbers with a decimal point become f64. Narrow these to a smaller type (u32, f32, etc.) by hand if your use case needs it — the generator picks the safe, widest default.