JSON to Rust
The result appears here.
Generate
Your document never leaves the browser.
Rust structs deriving serde, from a JSON sample — in your browser, never uploaded.
Paste a document and get Rust structs that serde_json reads and writes. The Cargo.toml lines the code needs are in a comment at the top, so the file compiles once it is dropped into a crate.
Each object becomes a pub struct deriving Debug, Clone, Serialize and Deserialize, with pub fields. snake_case keys stay as they are, since Rust uses the same convention; any other key gets a legal field name and a #[serde(rename)] holding the original, so gift-wrap is the field gift_wrap.
By default an absent key and a null both read as None. note is null in one line item, so it is Option<String>. gift-wrap is missing from one, so it is Option<bool> with default, which lets the key be left out, and skip_serializing_if, which leaves it out again when you write the struct back.
The sample, an order with two line items, pasted as the source
{
"order_id": 1042,
"placed_at": "2026-09-25T10:15:00Z",
"paid": true,
"customer": { "name": "Ada Lovelace", "email": "[email protected]" },
"items": [
{ "sku": "PEN-01", "qty": 2, "price": 3.5, "note": null },
{ "sku": "INK-07", "qty": 1, "price": 12, "note": "Fragile", "gift-wrap": true }
]
}The structs written for it
// Generated by myjsoneditor.com for serde and serde_json (Rust 2021).
// Cargo.toml: serde = { version = "1", features = ["derive"] }, serde_json = "1"
// An absent key and a null both read as None. Turn on "Distinguish null from absent" to keep them apart.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Customer {
pub name: String,
pub email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Item {
pub sku: String,
pub qty: i64,
pub price: f64,
pub note: Option<String>,
#[serde(rename = "gift-wrap", default, skip_serializing_if = "Option::is_none")]
pub gift_wrap: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub order_id: i64,
pub placed_at: String,
pub paid: bool,
pub customer: Customer,
pub items: Vec<Item>,
}Distinguish null from absent (serde_with) is off by default. Turn it on for a PATCH-style API where sending null means clear the value and leaving the key out means keep it: a field that may be both absent and null becomes Option<Option<T>>, using serde_with’s double_option, and serde_with = "3" joins the Cargo.toml comment.
A note that is text, then null, then missing
[
{ "id": 1, "note": "Fragile" },
{ "id": 2, "note": null },
{ "id": 3 }
]With Distinguish null from absent (serde_with) on
// Generated by myjsoneditor.com for serde and serde_json (Rust 2021).
// Cargo.toml: serde = { version = "1", features = ["derive"] }, serde_json = "1", serde_with = "3"
// A key that may be absent or null is Option<Option<T>>: None when absent, Some(None) when null.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RootItem {
pub id: i64,
#[serde(default, skip_serializing_if = "Option::is_none", with = "::serde_with::rust::double_option")]
pub note: Option<Option<String>>,
}
pub type Root = Vec<RootItem>;Nested structs are named from their keys, and a Vec of objects is named in the singular, so items is Vec<Item>. A field whose values differ in type becomes an enum marked #[serde(untagged)], with one variant per type that was seen, so serde tries each in turn. A key that is a Rust keyword is written as a raw identifier, so type is r#type.
An integer beyond i64 and u64 is typed serde_json::Number, and the header tells you to enable serde_json’s arbitrary_precision feature, without which the number is rounded. That feature is incompatible with untagged enums, so when it is needed the mixed fields fall back to serde_json::Value, and the header says that too.
A mixed list and a keyword
{ "code": [1, "A"], "type": "gift" }An untagged enum, and r#type
// Generated by myjsoneditor.com for serde and serde_json (Rust 2021).
// Cargo.toml: serde = { version = "1", features = ["derive"] }, serde_json = "1"
// An absent key and a null both read as None. Turn on "Distinguish null from absent" to keep them apart.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CodeValue {
Integer(i64),
String(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub code: Vec<CodeValue>,
pub r#type: String,
}The mixed list beside a 20-digit number
{ "account": 12345678901234567890, "code": [1, "A"] }serde_json::Number, and the list falls back to serde_json::Value
// Generated by myjsoneditor.com for serde and serde_json (Rust 2021).
// Cargo.toml: serde = { version = "1", features = ["derive"] }, serde_json = "1"
// An absent key and a null both read as None. Turn on "Distinguish null from absent" to keep them apart.
// Enable serde_json's "arbitrary_precision" feature, or serde_json::Number rounds integers outside i64 and u64.
// Mixed-type fields are serde_json::Value here, because that feature breaks #[serde(untagged)] enums.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub account: serde_json::Number, // past i64 — needs serde_json's arbitrary_precision feature
pub code: Vec<serde_json::Value>, // elements: one of: i64, String
}Inference and code generation both run inside this browser tab. The sample is never uploaded, stored on a server or logged, because the page has no server to send it to, and once loaded it carries on working with the connection off.
serde with the derive feature, and serde_json. Both lines are in the header comment, ready for Cargo.toml.
price has a fraction in the sample and qty never does. Change qty to u32 or another integer type if you know its range.
serde_json::from_str::<Root>(&text), with your root type in place of Root. It returns a Result whose error gives the line and column that failed.