JSON zu Rust-Struct: serde-Structs generieren (Guide 2026)
Fügen Sie Ihre Nutzlast in den JSON zu Rust Konverter ein und kopieren Sie das serde-Struct, das er erzeugt. Das ist der komplette Workflow von JSON zu Rust-Struct: nichts zu installieren, nichts wird hochgeladen, alles läuft in Ihrem Browser. Damit ist der Fall »Ich brauche jetzt sofort ein Struct« in Sekunden erledigt.
Ein Struct zu erzeugen und das richtige Struct zu erzeugen sind allerdings zwei verschiedene Probleme. Ein Konverter kann nur raten. Und hier erhöht Rust den Einsatz. In TypeScript verkommt eine falsche Vermutung zu einem laxen any, das Sie ignorieren können. Mit serde scheitert eine falsche Vermutung bereits bei der Deserialisierung: ein Gleitkommawert, der in einem i64 landet, ein unerwartetes null in einem nicht optionalen Feld – und serde_json::from_str gibt Ihnen ein Err statt Ihrer Daten zurück. Deshalb kommt es in Rust mehr als in den meisten Sprachen darauf an, das Struct richtig hinzubekommen.
Im Folgenden sehen Sie, wie die Typinferenz tatsächlich funktioniert, welche Regel zur Zahlentypisierung die meisten Konverter falsch machen, wann Sie zu #[serde(rename)] statt #[serde(rename_all)] greifen und wie Sie Datumsangaben, dynamische Schlüssel und Rust-Schlüsselwörter behandeln, damit die Ausgabe immer kompiliert.
JSON in Rust umwandeln
Die Umwandlung von JSON in Rust erfolgt in drei Schritten:
- Fügen Sie Ihr JSON ein. Werfen Sie ein Objekt, ein Array oder eine rohe API-Antwort in das Eingabefeld. Die Umwandlung läuft sofort und vollständig clientseitig.
- Passen Sie die Ausgabe an. Benennen Sie das Root-Struct um, schalten Sie die serde-Derives um, ergänzen Sie
DebugundCloneoder entfernen Sie diepub-Sichtbarkeit, damit alles zum Stil Ihres Crates passt. - Kopieren oder herunterladen. Schnappen Sie sich das erzeugte Rust mit einem Klick und fügen Sie es direkt in Ihr Projekt ein.
Das ist der komplette Ablauf. Falls Ihre Eingabe minifiziert ist oder Sie sich nicht sicher sind, ob sie gültig ist, schicken Sie sie zuerst durch einen JSON-Formatierer, damit der Konverter sauberes, wohlgeformtes JSON als Grundlage hat. Der Rest dieses Guides erklärt, wie Sie die Ausgabe lesen, damit Sie die Fälle nachbessern können, die ein Tool nicht von allein erschließen kann.
Wie sich JSON-Typen auf Rust abbilden
Jeder JSON-Wert hat ein Rust-Gegenstück, doch die Abbildung ist nicht ganz eins zu eins:
| JSON-Wert | Rust-Typ |
|---|---|
"text" | String |
42 | i64 (große Werte u64, darüber hinaus f64) |
3.14, 2e3 | f64 |
true / false | bool |
null | Option<T> (oder Option<serde_json::Value>) |
[1, 2, 3] | Vec<T> |
{ ... } | ein benanntes struct |
Bei Objekten fängt die eigentliche Arbeit an. Nehmen Sie eine typische REST-Nutzlast:
{
"id": 101,
"name": "Ada Lovelace",
"email": "ada@example.com",
"active": true,
"roles": ["admin", "user"]
}
Der Konverter erzeugt ein serde-fertiges Struct:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub id: i64,
pub name: String,
pub email: String,
pub active: bool,
pub roles: Vec<String>,
}
Jeder Skalar bildet sich auf einen primitiven Typ ab, und roles wird zu Vec<String>. Der eine JSON-Typ, der sich nicht sauber abbilden lässt, ist number: JSON hat einen einzigen Zahlentyp, während Rust Sie zwischen i64, u64 und f64 wählen lässt. Dieser Wahl ist weiter unten ein ganzer Abschnitt gewidmet, denn genau hier gehen die meisten Generatoren fehl.
Wie der Konverter Structs erschließt
Vier Regeln decken fast alles ab, was Sie ihm vorsetzen: ein Struct pro Objektform, schlüsselweises Zusammenführen bei Arrays, präzise Zahlentypisierung und idiomatische Feldnamen.
Strukturelle Inferenz: ein benanntes Struct pro Objekt
Jede eigenständige Objektform wird zu ihrem eigenen benannten Struct. Verschachtelte Objekte werden nicht eingebettet, sondern in separate, referenzierte Definitionen ausgelagert:
{ "repo": "serde", "owner": { "login": "dtolnay", "id": 100 } }
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub repo: String,
pub owner: Owner,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Owner {
pub login: String,
pub id: i64,
}
Das verschachtelte owner wird zu einem eigenen Owner-Struct, das über das Feld referenziert wird. Identische Formen werden dedupliziert, sodass sich zwei Felder mit derselben Struktur ein einziges Struct teilen, statt Kopien zu erzeugen.
Array-Zusammenführung und Option-Felder
Wenn Sie ein Array von Objekten übergeben, führt der Konverter sie Schlüssel für Schlüssel zusammen. Ein Schlüssel, der in einigen Elementen vorhanden ist, in anderen aber nicht, wird zu einem Option:
{ "users": [{ "id": 1, "nick": "x" }, { "id": 2 }] }
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub users: Vec<User>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: i64,
pub nick: Option<String>,
}
id erscheint in jedem Element und bleibt daher erforderlich. nick fehlt beim zweiten Benutzer und wird deshalb zu Option<String>. Beachten Sie, was hier nicht steht: kein #[serde(default)]. serde behandelt Option bereits als optional und deserialisiert einen fehlenden Schlüssel zu None, sodass das Attribut überflüssig wäre.
Korrekte Zahlentypisierung: i64, u64, f64
Das ist die Regel, die konkurrierende Tools auslassen, und genau die, die bei echten Nutzlasten zerbricht. Der Generator arbeitet mit drei Stufen. Eine Ganzzahl bildet sich auf i64 ab. Übersteigt ein Wert i64::MAX, wird er zu u64 hochgestuft. Jenseits von u64::MAX fällt er auf f64 zurück. Jedes Token mit Dezimalpunkt oder Exponent, etwa 1.0 oder 2e3, bildet sich unabhängig von seiner Größe auf f64 ab.
{ "user_id": 12000000000000000000, "balance": 19.99, "retries": 3 }
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub user_id: u64,
pub balance: f64,
pub retries: i64,
}
Hier ist user_id größer als i64::MAX, landet daher in u64 und lässt sich weiterhin verlustfrei hin- und zurückwandeln. balance trägt einen Dezimalpunkt und ist deshalb f64, keine Ganzzahl. Das ist wichtig, weil serde den Typ bei der Deserialisierung erzwingt. Geben Sie ihm ein Struct, das eine Snowflake- oder Twitter-artige ID als i32 typisiert, läuft der Wert über; typisieren Sie ein Gleitkommafeld als i64, gibt serde einen Fehler zurück, statt still abzuschneiden. Die Drei-Stufen-Regel sorgt dafür, dass ein generiertes rust struct from json deserialisiert, statt bei der ersten großen ID zu panicken.
snake_case-Felder und #[serde(rename)]
JSON-Schlüssel sind häufig camelCase; idiomatische Rust-Felder sind snake_case. Der Konverter benennt das Feld um und ergänzt ein #[serde(rename)], das es exakt auf den JSON-Schlüssel zurückabbildet:
{ "login": "octocat", "publicRepos": 15, "followerCount": 9001, "createdAt": "2011-01-25" }
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub login: String,
#[serde(rename = "publicRepos")]
pub public_repos: i64,
#[serde(rename = "followerCount")]
pub follower_count: i64,
#[serde(rename = "createdAt")]
pub created_at: String,
}
Ein Schlüssel, der bereits snake_case ist, etwa login, erhält kein Rename. Die übrigen bilden sich sauber auf das Wire-Format zurück, während sich Ihr Rust-Code natürlich liest.
#[serde(rename)] vs. #[serde(rename_all)]
Das Tool gibt pro Feld ein #[serde(rename)] aus, weil das immer funktioniert – selbst wenn eine Nutzlast camelCase, snake_case und unregelmäßige Schlüssel im selben Objekt mischt. Es muss nie über eine gemeinsame Konvention mutmaßen, die es vielleicht gar nicht gibt.
Wenn jedoch alle Felder eines Structs derselben Konvention folgen, können Sie diese Attribute zu einem einzigen Rename auf Container-Ebene zusammenfassen. Löschen Sie die feldweisen Zeilen und setzen Sie ein einziges #[serde(rename_all = "camelCase")] auf das Struct:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub login: String,
pub public_repos: i64,
pub follower_count: i64,
pub created_at: String,
}
Beide Varianten deserialisieren dasselbe JSON. Nutzen Sie das feldweise rename für gemischte oder unregelmäßige Schlüssel; greifen Sie zu rename_all, wenn ein Struct einheitlich ist und Sie weniger Rauschen möchten. serde bringt für dasselbe Attribut auch weitere Konventionen mit, etwa "snake_case", "kebab-case" und "SCREAMING_SNAKE_CASE".
Rust-Schlüsselwörter und Nicht-Identifier-Schlüssel
JSON-Schlüssel sind bloß Zeichenketten, also hindert nichts eine API daran, einen Schlüssel namens type oder match zu senden – beides reservierte Wörter in Rust. Der Generator bereinigt sie zu einem gültigen Bezeichner samt Rename:
{ "type": "user", "match": true, "first-name": "Ada" }
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
#[serde(rename = "type")]
pub type_: String,
#[serde(rename = "match")]
pub match_: bool,
#[serde(rename = "first-name")]
pub first_name: String,
}
Die Form mit angehängtem Unterstrich (type_) wird bewusst einem rohen Bezeichner wie r#type vorgezogen. Rohe Bezeichner können nicht jedes Schlüsselwort in jeder Position ausdrücken; insbesondere self, crate und super werden als rohe Bezeichner abgelehnt, weshalb ein Ansatz aus Bereinigen plus Rename der einzige ist, der immer kompiliert. Nicht-Identifier-Schlüssel wie first-name oder ein mit einer Ziffer beginnendes 2fa erhalten dieselbe Behandlung: einen gültigen Rust-Namen und ein Rename zurück auf den wörtlichen JSON-Schlüssel.
Deserialisieren mit serde_json
Sobald Sie das Struct haben, fügen Sie die Abhängigkeiten zu Cargo.toml hinzu:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Danach genügt eine einzige Zeile zum Parsen. Dieses Beispiel ist vollständig und kompiliert unverändert:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub id: i64,
pub name: String,
pub email: String,
pub active: bool,
pub roles: Vec<String>,
}
fn main() -> Result<(), serde_json::Error> {
let data = r#"{"id":101,"name":"Ada Lovelace","email":"a@ex.com","active":true,"roles":["admin"]}"#;
let root: Root = serde_json::from_str(data)?;
println!("{} has {} role(s)", root.name, root.roles.len());
Ok(())
}
Das Deserialize-Derive erledigt die ganze Arbeit. Verwenden Sie serde_json::from_str für eine Zeichenkette, from_slice für einen Byte-Slice oder from_reader für eine Datei oder einen HTTP-Body und serde_json::to_string, um einen Wert wieder in JSON zu serialisieren. Das ist der Lohn dafür, die Typen richtig zu wählen: Das erzeugte serde derive ist ein Laufzeit-Validator, sodass ein serde_json-Deserialize-Aufruf, der Ok zurückgibt, eine Garantie dafür ist, dass die Daten zu Ihrem Struct passen – nicht bloß die Überzeugung eines Compilers, dass es so sei.
Datumsangaben, dynamische Schlüssel und unbekannte Felder
Manche Formen lassen sich nicht auf ein schlichtes Struct-Feld abbilden, und der Generator gibt ihnen einen sinnvollen Standardwert, den Sie anschließend von Hand verschärfen.
Datumsangaben. JSON kennt keinen Datumstyp, daher kommt ein ISO- oder RFC-3339-Zeitstempel als schlichte String an. Für echte Datumsverarbeitung wechseln Sie das Feld auf einen chrono-Typ und aktivieren chronos serde-Feature (chrono = { version = "0.4", features = ["serde"] }). serde parst RFC 3339 dann automatisch:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub name: String,
pub created_at: DateTime<Utc>,
}
Dynamische Schlüssel. Wenn Schlüssel zur Laufzeit variieren, etwa eine Zuordnung von IDs zu Werten, ist ein festes Struct die falsche Form. Verwenden Sie eine HashMap mit String als Schlüssel:
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub scores: HashMap<String, i64>,
}
Unbekannte Zusatzfelder. Um ein typisiertes Struct beizubehalten und trotzdem Schlüssel zu erfassen, die Sie nicht modelliert haben, ergänzen Sie ein #[serde(flatten)]-Feld, das von einer HashMap getragen wird. Für einen Wert, der wirklich dynamisch ist, ist serde_json::Value der Auffangtyp. Wenn Ihr eigentliches Anliegen darin besteht, Struktur zu validieren, statt Typen zu generieren, behandelt der Guide zur JSON-Schema-Validierung, wie Sie einen Vertrag durchgängig erzwingen.
json-to-rust vs. quicktype vs. typeshare vs. manuell
Es gibt keinen einzig besten Weg, Rust-Typen aus JSON zu erzeugen. Es hängt davon ab, wo das JSON liegt und in welche Richtung Sie konvertieren.
| Ansatz | Am besten für | Hinweis |
|---|---|---|
| Online-Konverter (dieses Tool, transform.tools) | Einmalige Umwandlungen, sensible Nutzlasten, keine Installation | Erschließt aus einer Stichprobe, vollständig clientseitig |
| quicktype | Ausgabe für mehrere Sprachen, Codegen in Pipelines | Ebenfalls stichprobengetrieben; CLI oder Web |
| typeshare | Bestehende Rust-Typen mit TS, Swift, Kotlin teilen | Umgekehrte Richtung: Rust zu anderen Sprachen |
| schemafy / typify | Erzeugung aus einem JSON-Schema | Eingabe ist ein Schema, keine Stichprobe |
| Structs von Hand schreiben | Winzige Nutzlasten, serde lernen | Volle Kontrolle, mühsam und fehleranfällig im großen Maßstab |
Der Unterschied, den man sich einprägen sollte: Ein Online-Konverter und quicktype erschließen Typen beide aus einer Stichprobe von JSON, während typeshare andersherum läuft und annotierte Rust-Typen in TypeScript oder Swift überführt. schemafy und typify nehmen ein JSON-Schema als Eingabe, keine Beispieldaten. Falls Ihre Codebasis TypeScript statt Rust ist, gilt derselbe stichprobengetriebene Ansatz mit dem JSON zu TypeScript Konverter, und der Guide zu JSON-zu-TypeScript-Interfaces behandelt diese Seite ausführlich.
Typische Fallstricke beim Generieren von Rust aus JSON
Generierte Structs sind ein Ausgangspunkt. Achten Sie auf Folgendes, bevor Sie der Ausgabe bei Live-Daten vertrauen.
- Eine einzelne Stichprobe offenbart selten jede Form. Optionale Felder lassen sich nur aus mehreren Array-Elementen erschließen. Fügen Sie ein repräsentatives Array ein, damit die
Option-Inferenz stimmt und nicht bloß eine Vermutung aus einem glücklich gewählten Objekt ist. - Leere oder gemischte Arrays fallen auf
serde_json::Valuezurück. Das ist die ehrliche Antwort, wenn es nichts zu erschließen gibt. Geben Sie eine reichhaltigere Stichprobe ein, um einen konkreten Elementtyp zu erhalten. - Verengen Sie eine große ID nicht auf
i32. Snowflake- und ähnliche IDs überschreiten 2^53 und lassen ein 32-Bit-Feld überlaufen. Behalten Sie das generiertei64oderu64bei. - Ein stets
null-Feld wird zuOption<serde_json::Value>, keinem schlichten Optional. Das Tool hat keinen Typ zum Erschließen, behandeln Sie es also als Platzhalter und geben Sie ihm einen echten Typ, sobald Sie einen kennen. serde_json::Valuebenötigt dieserde_json-Abhängigkeit. Enthält die AusgabeValueund IhrerCargo.tomlfehlt das Crate, kompiliert der Code nicht.- Ein als
Stringbelassenes Datum kann nicht rechnen. Wenn Sie Zeitstempel vergleichen oder formatieren möchten, wechseln Sie auf einen chrono-Typ, statt Zeichenketten von Hand zu parsen.
Häufig gestellte Fragen
Warum lehnt serde mein JSON ab, obwohl das Struct richtig aussieht?
Fast immer eine Typinkongruenz. Die beiden üblichen Ursachen sind ein Gleitkommawert, der in einem i64-Feld landet, und ein Schlüssel, der mal fehlt, aber nicht als Option typisiert ist. serde erzwingt den Typ bei der Deserialisierung, ändern Sie das Feld also auf f64 oder Option<T> oder generieren Sie das Struct aus einer repräsentativen Stichprobe neu.
Welchen Rust-Zahlentyp sollte eine JSON-Ganzzahl erhalten?
Standardmäßig i64. Stufen Sie auf u64 hoch, wenn ein Wert i64::MAX übersteigt, und fallen Sie jenseits von u64::MAX auf f64 zurück. Jede Zahl mit Dezimalpunkt oder Exponent bildet sich unabhängig von der Größe auf f64 ab, weil serde einen Gleitkommawert ablehnt, der in ein Ganzzahlfeld deserialisiert wird.
Wie mache ich ein Feld optional, wenn JSON es weglässt?
Typisieren Sie es als Option<T>. serde behandelt Option automatisch als optional und deserialisiert einen fehlenden Schlüssel zu None, sodass Sie kein #[serde(default)] brauchen. Der Konverter kennzeichnet ein Feld als Option, wenn einigen der beprobten Elemente der Schlüssel fehlt.
Sollte ich #[serde(rename)] oder #[serde(rename_all)] verwenden?
Verwenden Sie das feldweise #[serde(rename)], wenn eine Nutzlast Namensstile mischt; das funktioniert immer. Folgen alle Felder eines Structs einer Konvention, löschen Sie die feldweisen Attribute und setzen stattdessen ein einziges #[serde(rename_all = "camelCase")] auf das Struct. Beide deserialisieren identisch.
Wie behandle ich JSON-Schlüssel, die Rust-Schlüsselwörter sind, etwa type?
Der Generator gibt type_ mit einem #[serde(rename = "type")] aus, das auf den ursprünglichen Schlüssel zurückabbildet. Das ist robuster als ein roher Bezeichner wie r#type, weil rohe Bezeichner self, crate und super nicht in jeder Position abdecken können.
Warum werden JSON-Datumsangaben als String statt als Datumstyp typisiert?
JSON kennt keinen Datumstyp, ein Zeitstempel ist also auf der Leitung nur eine Zeichenkette, und String ist der ehrliche Standard. Für echte Datumsverarbeitung ändern Sie das Feld auf chronos DateTime<Utc> oder NaiveDate und aktivieren chronos serde-Feature; serde parst dann RFC 3339 für Sie.
Wie deserialisiere ich JSON in das generierte Struct?
Fügen Sie serde_json zu Cargo.toml hinzu und schreiben Sie dann let root: Root = serde_json::from_str(json)?;. Das Deserialize-Derive erledigt den Rest. Verwenden Sie from_slice für einen Byte-Slice und from_reader für eine Datei oder einen HTTP-Body.
Ist mein JSON privat, wenn ich einen Online-Konverter von JSON zu Rust-Struct nutze?
Ja. Die Umwandlung läuft zu 100 % in Ihrem Browser mit JavaScript. Ihr JSON – einschließlich Tokens, IDs und Kundendaten – verlässt nie die Seite und wird nie an einen Server gesendet. Wenn Sie so weit sind, probieren Sie den JSON zu Rust Konverter mit Ihrer eigenen Nutzlast aus.