JSON to Rust Struct Generator
Convert any JSON object into Rust structs with serde Serialize and Deserialize derives, correct Rust type mappings, Option for nullable fields, nested struct decomposition, and serde(rename) attributes for camelCase JSON fields — paste directly into your Rust project and compile.
Convert any JSON object into Rust structs with serde Serialize and Deserialize derives, correct Rust type mappings, Option for nullable fields, nested struct decomposition, and serde(rename) attributes for camelCase JSON fields — paste directly into your Rust project and compile.
This tool is designed to provide a seamless experience for developers by handling complex operations directly in your browser with maximum speed and security.
Rust has no runtime reflection. There is no equivalent of JavaScript's JSON.parse() that returns a loosely-typed object you can access with dynamic property lookups. In Rust, every piece of data must have a type known at compile time, and the compiler rejects code that tries to access a field that does not exist on the declared type. This means that to work with JSON in Rust, you must first describe the structure you expect — in code, as a Rust struct — and then deserialize the JSON into that struct. If the JSON does not match the struct, deserialization fails at runtime with a clear error rather than silently returning undefined.
serde is the de-facto Rust serialization framework. The name stands for Serialize/Deserialize. It works through Rust's procedural macro system — you add the #[derive(Serialize, Deserialize)] attribute to a struct definition, and the serde macros generate all the serialization and deserialization code at compile time. The generated code is zero-cost: there is no runtime overhead, no heap allocation for reflection metadata, and no dynamic dispatch. serde_json is the serde data format implementation for JSON specifically — it provides serde_json::from_str() to deserialize a JSON string into a Rust struct, serde_json::to_string() to serialize a struct to JSON, and serde_json::Value as a weakly-typed escape hatch for JSON whose structure is not known at compile time.
The struct field names in Rust conventionally use snake_case (user_id, created_at, is_active), but JSON field names from REST APIs frequently use camelCase (userId, createdAt, isActive) or other conventions. serde handles this mismatch through the serde(rename = "fieldName") attribute on individual fields, or the serde(rename_all = "camelCase") attribute on the entire struct which automatically renames all fields during serialization and deserialization. Getting this mapping right is essential — a struct field named user_id with no rename attribute will not deserialize from a JSON field named userId. The compiler will not warn you; the field will simply be left at its default value or the deserialization will fail silently depending on how the field is configured.
Paste a JSON object and the tool generates the complete Rust struct definitions needed to deserialize it with serde. Type inference works as follows: JSON strings become String, integers become i64 (covers the full JSON integer range), floats become f64, booleans become bool, JSON arrays become Vec with the element type inferred, JSON null values become Option where T is inferred from the non-null value context, and nested JSON objects become separate named struct types. Every struct gets the #[derive(Debug, Serialize, Deserialize)] derive macro added automatically. For field name mapping, the tool generates serde(rename_all = "camelCase") on structs where it detects camelCase JSON field names. For fields where the Rust snake_case name differs from the JSON key, it adds explicit #[serde(rename = "jsonFieldName")] attributes on the individual fields. The generated Rust field names follow Rust naming conventions — is_active not isActive, created_at not createdAt — and the rename attributes ensure serde maps them correctly to the JSON keys. The output includes use statements for the serde crate and the full struct definitions. It is compile-ready after adding serde and serde_json to your Cargo.toml dependencies. Nested objects generate separate struct definitions ordered so that inner types are defined before the outer types that reference them — the order that Rust's type checker requires. After generating, you typically rename the top-level struct from the auto-generated name to something meaningful (User, ApiResponse, ProductRecord) and adjust any Option fields that you know are always present to use T directly.
1. Paste your JSON into the Input JSON field — use a real API response sample that contains all the fields you need to access in your Rust code. Include the most complete version of the response you have: if some fields are sometimes null and sometimes present, include a sample where they have actual values so the tool can infer the correct inner type for the Option. Click Load Example to see a multi-field nested struct before working with your own data.
2. Click Convert to Rust Struct — the tool parses every field, maps it to a Rust type, generates serde derive macros, handles rename attributes for camelCase fields, creates separate struct definitions for nested objects, and outputs the complete Rust code with use statements.
3. Review the generated struct names and rename the top-level struct — the auto-generated name is a placeholder. Change it to the domain name of the type you are working with: User, GithubRepo, StripeEvent, SpotifyTrack. Rust struct names are PascalCase and appear in your function signatures, error messages, and documentation. Choose names that make the code self-documenting.
4. Decide which Option fields should stay optional and which should become required — the tool generates Option for any field that appears as null in your sample. For fields you know are always present in production API responses, change Option to String and Option to i64. For genuinely optional fields, keep Option and remember you will need to use if let, unwrap_or, or ? propagation to access the inner values safely.
5. Add serde and serde_json to your Cargo.toml and paste the generated structs into your project — add serde = { version = "1", features = ["derive"] } and serde_json = "1" under [dependencies]. Paste the structs into a models.rs or types.rs file. Deserialize using let data: YourStruct = serde_json::from_str(json_string)? and serialize using serde_json::to_string(&data)?.
Writing serde struct definitions by hand is not hard — it is just slow and unforgiving about mistakes. A JSON response from a GitHub API endpoint, a Stripe webhook payload, a Kubernetes API response, a Spotify track object — these things have 20, 30, 50 fields. Every field needs a Rust field name, a type, and possibly a rename attribute if the JSON uses camelCase. Every nested object needs its own struct. Writing that by hand for a new API integration takes 30–45 minutes. This tool takes the mechanical part away and gives you the scaffold in seconds, so the time you spend is on the parts that actually need judgment: which fields should be Option versus required, which types need to be more specific (a status field that should be an enum, not a String), and what the structs should be named. The Option question is worth dwelling on. In JSON, any field can be missing or null — the schema does not enforce presence. But in your Rust application, there is usually a clear distinction between "this field is sometimes absent by design" (a user's middle name, an optional description) and "this field should always be present but I'm being defensive" (a user's email address). The tool generates Option for fields that appear as null in your sample, but you should actively decide for each field whether it is genuinely optional. Using Option everywhere is defensive but makes your code verbose — you have to unwrap or pattern match every field access. Using T (non-optional) for fields that are actually always present makes the code cleaner and any absence becomes a deserialization error, which is often the right behavior: if the API breaks its contract and omits a required field, you want to know immediately. One scenario I keep coming back to: Actix-web and Axum (the dominant Rust web frameworks) use serde for request body deserialization through extractors. An Axum handler that accepts Json will automatically deserialize the request body into MyRequest and return 422 Unprocessable Entity if the body does not match the struct. The struct definition is the entire input validation layer. Getting the types right — knowing which fields are Option, which should have serde(default), and which should fail if absent — defines the behavior of your API endpoint. This tool gives you a usable starting point for that struct in under a minute.
serde derive macros included — every struct gets #[derive(Debug Serialize Deserialize)] automatically so the generated code works with serde_json immediately without any manual macro additions
Handles camelCase JSON with rename_all — detects camelCase JSON field names and generates the serde(rename_all = "camelCase") attribute so your Rust snake_case field names map correctly without manual rename attributes on every field
Option for nullable fields — JSON null values generate Option with the correct inner type inferred so nullable fields are represented idiomatically in Rust
Nested struct decomposition — generates separate named pub struct definitions for nested JSON objects ordered correctly so inner types are defined before outer types that reference them
Correct Rust type inference — strings become String integers become i64 floats become f64 booleans become bool and arrays become Vec with the element type inferred
Cargo.toml and usage comments included — the output shows the exact dependency lines to add and example serde_json deserialization code
100% browser-based — your JSON API response data never leaves your machine which matters when API responses contain credentials internal endpoint paths or proprietary data models
Compile-ready scaffold — the output compiles after adding the Cargo.toml dependencies with only naming and type precision decisions remaining
Generating serde structs for REST API response bodies when building Rust HTTP client libraries or CLI tools with reqwest
Creating request body structs for Axum or Actix-web JSON extractors (Json) that automatically validate incoming request bodies
Bootstrapping struct definitions for Rust services that consume Kafka messages serialized as JSON
Generating serde types for configuration file parsing with serde_json or toml when config shares JSON-compatible types
Creating struct definitions when migrating a Node.js or Python service to Rust and needing to match existing JSON API contracts
Generating Rust types for webhook payload handling in a Rust backend service
Bootstrapping model types for database serialization when using a NoSQL database that stores JSON documents accessed from Rust
Learning serde syntax by seeing generated derive macros and rename attributes for different JSON structures
Example Input
{
"id": 1,
"userName": "priya_singh",
"email": "priya@learnhubly.com",
"isActive": true,
"score": 98.5,
"tags": ["developer", "admin"],
"profile": {
"bio": "Principal Software Engineer",
"skills": ["Go", "Rust", "TypeScript"]
},
"deletedAt": null
}Example Output
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Profile {
pub bio: String,
pub skills: Vec,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
pub id: i64,
pub user_name: String,
pub email: String,
pub is_active: bool,
pub score: f64,
pub tags: Vec,
pub profile: Profile,
pub deleted_at: Option,
}
// Cargo.toml dependencies:
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
// Usage:
// let user: User = serde_json::from_str(json_str)?;
// let json: String = serde_json::to_string(&user)?;Serde Dependency Missing: The generated code uses serde derive macros and serde_json. If these are not in your Cargo.toml, the code will not compile. Add serde = { version = '1', features = ['derive'] } and serde_json = '1' under [dependencies] in your Cargo.toml. The features = ['derive'] part is essential — without it the #[derive(Serialize, Deserialize)] macros are not available even though the serde crate is present.
Invalid JSON Input: The tool requires valid JSON before it can generate struct definitions. Malformed JSON — missing commas, unquoted keys, trailing commas, single quotes — causes the conversion to fail. Use the JSON Formatter and Validator tool to fix any syntax errors first. The error message from the converter will indicate where the parsing stopped.
camelCase Field Mismatch Causes Silent Deserialization Failure: If the generated struct does not include serde(rename_all) or individual serde(rename) attributes and the JSON uses camelCase field names while the Rust fields use snake_case, serde will silently fail to deserialize those fields — they will be left at their default values if the field type implements Default, or the entire deserialization will fail with a missing field error if the field has no default. This is the most common Rust JSON bug. Always verify that the rename attributes are present and correct by testing deserialization with a sample payload before shipping the code.
Option Fields Causing Compile Errors When Accessed Directly: Fields generated as Option or Option cannot be used directly where a String or i64 is expected. You must handle the Option explicitly: use if let Some(value) = my_struct.deleted_at { ... }, use my_struct.deleted_at.unwrap_or_default(), or use the ? operator with Option methods. If you converted an Option field back to a non-optional type in the struct but the JSON sometimes omits that field, deserialization will fail at runtime with a missing field error — the strictness is correct behavior, but be sure to only remove Option when you are certain the field is always present.
Nested Struct Type Name Conflicts: The tool generates struct names from the JSON field names. If your JSON has two nested objects at different levels that would generate the same struct name, or if the generated name conflicts with an existing type in your Rust codebase, you will get a duplicate type definition compile error. Rename the conflicting generated struct before using it. Rust struct names must be unique within a module and PascalCase by convention.
Using String for all text fields when more specific types would be better
Fix: The tool generates String for all JSON string fields because it cannot infer semantic meaning from a sample value. But many string fields in real API responses represent more specific types. A status field with values like 'active', 'inactive', 'pending' should be a Rust enum, not a String — an enum gives you exhaustive match patterns, prevents typos, and makes illegal states impossible. A timestamp field like created_at with a value of '2026-05-25T12:00:00Z' might be better as a chrono::DateTime with a serde datetime attribute rather than a String. A uuid field like '550e8400-e29b-41d4-a716-446655440000' could use the uuid crate's Uuid type with serde support. Review generated String fields and replace with more precise types where your domain logic benefits from it.
Not adding #[serde(default)] to fields that are sometimes absent from the JSON
Fix: When a JSON field is absent (not null, but completely missing from the object), serde's default behavior depends on whether the field is Option or T. For Option, a missing field deserializes to None by default — that is the expected behavior. For T, a missing field causes a deserialization error unless you add #[serde(default)] to the field, which tells serde to use the type's Default trait value (empty string for String, 0 for i64, false for bool) when the field is absent. If your generated struct has non-optional fields and your API sometimes omits them in the response, add #[serde(default)] or change the field to Option. The choice between them is meaningful: Option communicates 'this field is intentionally absent sometimes', while serde(default) communicates 'this field should have a sensible zero value when absent'.
Cloning data unnecessarily because the generated struct uses owned String types everywhere
Fix: The generated structs use owned String and Vec for all string and array fields, which is correct for deserialized data you own. However, if you are passing struct values to functions, returning them from functions, or storing references to them, Rust's ownership system requires either transferring ownership (move) or borrowing (&). Cloning (struct.clone()) to avoid ownership issues works but is costly for large structs. If your use case involves borrowing the deserialized data rather than owning it — passing a struct to a function that only needs to read it — pass a reference: fn process(user: &User) rather than fn process(user: User). The generated structs can derive Clone by adding it to the derive list: #[derive(Debug, Clone, Serialize, Deserialize)] for cases where cloning is necessary.
Generating structs from a minimal JSON sample that does not represent all possible field states
Fix: The tool infers types from the sample you provide. If a field is always present in your sample, it generates a non-optional T. If a field is absent or null in your sample but present in some real API responses, the generated type will not include that field. If a field has a specific value type in your sample (a string) but can be a different type in other responses (serde_json::Value for a mixed-type field), the generated type will be wrong. Use the most complete and varied sample you have — ideally one with all optional fields populated and any union-type fields present. If you do not have a representative sample, check the API documentation for the complete field list and their types.
Trying to deserialize a JSON array at the top level into a struct
Fix: The tool generates structs for JSON objects (starting with {). If the API returns a top-level JSON array (starting with [), you cannot deserialize it directly into a struct — you need a Vec. Generate the struct from a single element of the array, then use: let items: Vec = serde_json::from_str(json_str)? for a top-level array, or let items: Vec = serde_json::from_value(response['items'])? if the array is a field within a wrapper object. For the wrapper object case, generate the struct from the full response object — the tool will create a struct with a Vec field for the array.
JSON Cheatsheet
Interactive reference guide for valid JSON syntax, data types, parsing/stringifying methods, schema validation rules, and language integrations.
Regex Cheatsheet
Interactive guide to Regex anchors, character classes, quantifiers, lookarounds, capturing groups, and search flags.
HTTP Headers Cheatsheet
Complete guide to standard and security HTTP headers including Authorization, CORS control, caching policies, and CSP directives.
SQL Cheatsheet
Complete guide to SQL statements including SELECT queries, WHERE filters, aggregate functions, JOIN types, and DDL commands.
Does it handle Option types?
Yes. Any JSON field that appears as null in the sample generates Option in the Rust struct, where T is inferred from context. A null string field becomes Option, a null integer becomes Option. serde deserializes a JSON null value as None and a present value as Some(value). Fields that are completely absent from the JSON behave differently from null — see the serde(default) discussion in Common Mistakes for the distinction. After generating, review each Option field and decide whether it genuinely needs to be optional or whether you know it will always be present — non-optional fields produce cleaner downstream code.
How do I add the serde dependency to my project?
Add these two lines under [dependencies] in your Cargo.toml: serde = { version = '1', features = ['derive'] } and serde_json = '1'. The features = ['derive'] is mandatory — without it, the derive macros (#[derive(Serialize, Deserialize)]) are not compiled and the generated code will not compile. If you are using async Rust with Tokio, you likely already have serde in your dependency tree through other crates like axum or reqwest — check Cargo.lock first to avoid version conflicts, though serde 1.x is backward compatible so duplicates are not usually a problem.
What is the difference between serde_json::from_str and serde_json::from_value?
serde_json::from_str(json_string) deserializes a raw JSON string (&str or String) directly into a typed Rust struct — this is the most efficient approach when you receive JSON as text (from an HTTP response body, a file, a message queue). serde_json::from_value(json_value) deserializes a serde_json::Value (the weakly-typed JSON representation) into a struct — use this when you have already parsed the JSON into a Value for inspection or manipulation and want to convert a portion of it into a typed struct. For HTTP responses with reqwest, the typical pattern is: let response: MyStruct = reqwest::get(url).await?.json::().await? which handles the deserialization internally.
Is it safe to use this with internal or proprietary API response data?
Yes. Everything runs in your browser. The JSON you paste is processed by JavaScript locally and never transmitted to any server. This matters for API responses that contain authentication tokens, internal endpoint paths, customer data, or any other information you would not want a third-party service to process. The generated Rust code is just text — copy it, close the tab, and the data is gone.
How do I handle JSON fields whose type can be multiple different types?
Some APIs return fields that can be either a string or an integer, or either a specific object or null with a different null representation than serde expects. For these cases, the generated String or i64 type will not work universally. The options are: use serde_json::Value as the field type (completely flexible but requires runtime type checking), write a custom deserializer with #[serde(deserialize_with = 'my_fn')] that handles the type ambiguity, or use an untagged enum: #[serde(untagged)] enum StringOrInt { String(String), Int(i64) }. The untagged enum approach is the most idiomatic Rust solution — it preserves type safety while handling multiple possible representations.
How do I use the generated struct with reqwest for HTTP requests?
With reqwest (the dominant Rust HTTP client): for deserializing a response body into a struct, use let response: MyStruct = client.get(url).send().await?.json::().await?. The .json() method handles the deserialization using serde_json. For sending a struct as a JSON request body, use .json(&my_struct) on the request builder: client.post(url).json(&my_struct).send().await?. This serializes the struct to JSON using serde_json::to_string() internally. Both directions require the struct to implement Deserialize (for receiving) and Serialize (for sending) — the generated #[derive(Serialize, Deserialize)] handles both.
Can I add validation to the generated struct fields?
serde itself does not provide field-level validation beyond type checking. For input validation on deserialized data, the most popular approach in the Rust ecosystem is the validator crate: add #[derive(Validate)] to the struct and add #[validate(...)] attributes to fields (validate(email), validate(length(min = 1, max = 255)), validate(range(min = 0, max = 100))). Call data.validate()? after deserialization. Actix-web has a Json extractor variant that runs validation automatically. Another approach is newtype wrappers — instead of pub email: String, define a newtype Email(String) that validates the format in its TryFrom implementation, then use pub email: Email in the struct. The newtype approach makes invalid states unrepresentable at the type level.
What should I know about serde performance for high-throughput services?
serde_json is fast — benchmarks consistently put it among the fastest JSON parsers in any language, competitive with simdjson. The derive macros generate code at compile time with no runtime reflection overhead. For most applications, serde_json performance is not a bottleneck. If you are processing very high message volumes (millions of messages per second), consider: using simd_json (a SIMD-accelerated JSON parser with serde compatibility) as a drop-in replacement for serde_json, using serde_json::from_slice instead of from_str to avoid an extra UTF-8 validation step, and minimizing allocations by reusing buffers. For Kafka consumers and other streaming scenarios, the bottleneck is almost always I/O and network, not JSON deserialization.
How to Convert JSON to Dart Classes for Flutter – Complete In-Depth Guide (2026)
How to convert JSON to Dart classes for Flutter in 2026. In-depth guide covering Dart model best practices, null safety, immutable classes, code generation with json_serializable & freezed, Flutter integration with Riverpod, and real-world architecture tips from a Principal Software Engineer with 15+ years experience.
What is JSON? How to Format, Validate & Use It (Complete Guide 2026)
What is JSON? How to Format, Validate & Use It (Complete Guide 2026). In-depth explanation of JSON syntax, real-world use cases, formatting best practices, common mistakes, advantages, disadvantages, and expert tips from a Principal Software Engineer with 15+ years experience.
How to validate JSON online (step-by-step guide)
Invalid JSON can break your application. Follow this guide to quickly validate and fix your JSON data.
Recent Activity
No recent activity