JSON na strukturę Rust: generuj struktury serde (2026)
Wystarczy wkleić payload do Konwertera JSON na struktury Rust i skopiować wygenerowaną strukturę serde. To cały proces konwersji JSON na strukturę Rust: nic nie trzeba instalować, nic nie jest wysyłane, wszystko dzieje się w przeglądarce. Załatwia to przypadek „potrzebuję struktury już teraz” w kilka sekund.
Wygenerowanie struktury i wygenerowanie właściwej struktury to jednak dwa różne problemy. Konwerter może tylko zgadywać. A tutaj Rust podnosi stawkę. W TypeScripcie błędne zgadnięcie degeneruje się do luźnego any, który można zignorować. Przy serde błędne zgadnięcie kończy się porażką na etapie deserializacji: liczba zmiennoprzecinkowa wrzucona do i64, nieoczekiwany null w polu bez opcjonalności — i serde_json::from_str zwraca Err zamiast danych. Dlatego poprawna struktura ma w Rust większe znaczenie niż w większości języków.
Poniżej opisujemy, jak faktycznie działa wnioskowanie typów, jaką regułę typowania liczb większość konwerterów pomija, kiedy sięgnąć po #[serde(rename)], a kiedy po #[serde(rename_all)], oraz jak obsłużyć daty, dynamiczne klucze i słowa kluczowe Rust, aby wynik zawsze się kompilował.
Jak przekonwertować JSON na Rust
Konwersja JSON na Rust przebiega w trzech krokach:
- Wklej JSON. Wrzuć obiekt, tablicę lub surową odpowiedź API do pola wejściowego. Konwersja uruchamia się natychmiast i w całości po stronie klienta.
- Dostrój wynik. Zmień nazwę głównej struktury, przełącz makra derive serde, dodaj
DebugiClonelub usuń widocznośćpub, aby dopasować się do stylu swojego crate’a. - Skopiuj lub pobierz. Pobierz wygenerowany kod Rust jednym kliknięciem i wklej go prosto do projektu.
To cała ścieżka transakcyjna. Jeśli dane wejściowe są zminifikowane albo nie masz pewności, czy są poprawne, najpierw przepuść je przez Formatowanie JSON, aby konwerter dostał czysty, dobrze sformułowany JSON. Reszta tego przewodnika wyjaśnia, jak czytać wynik, aby poprawić przypadki, których narzędzie nie jest w stanie samo wywnioskować.
Jak typy JSON mapują się na Rust
Każda wartość JSON ma swój odpowiednik w Rust, ale mapowanie nie jest do końca jeden do jednego:
| Wartość JSON | Typ Rust |
|---|---|
"text" | String |
42 | i64 (duże wartości u64, powyżej f64) |
3.14, 2e3 | f64 |
true / false | bool |
null | Option<T> (lub Option<serde_json::Value>) |
[1, 2, 3] | Vec<T> |
{ ... } | nazwana struct |
Cała robota dzieje się przy obiektach. Weźmy typowy payload REST:
{
"id": 101,
"name": "Ada Lovelace",
"email": "ada@example.com",
"active": true,
"roles": ["admin", "user"]
}
Konwerter tworzy strukturę gotową dla serde:
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>,
}
Każdy skalar mapuje się na typ prosty, a roles staje się Vec<String>. Jedynym typem JSON, który nie mapuje się gładko, jest number: JSON ma jeden typ liczbowy, podczas gdy Rust każe wybierać między i64, u64 i f64. Ten wybór jest tematem całej sekcji poniżej, bo to właśnie tam większość generatorów popełnia błąd.
Jak konwerter wnioskuje struktury
Cztery reguły obejmują niemal wszystko, co mu podasz: jedna struktura na kształt obiektu, scalanie klucz po kluczu dla tablic, precyzyjne typowanie liczb i idiomatyczne nazwy pól.
Wnioskowanie strukturalne: jedna nazwana struktura na obiekt
Każdy odrębny kształt obiektu staje się własną nazwaną strukturą. Zagnieżdżone obiekty nie są wstawiane w linii — zostają wyniesione do osobnych, referowanych definicji:
{ "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,
}
Zagnieżdżony owner staje się własną strukturą Owner, do której odwołuje się pole. Identyczne kształty są deduplikowane, więc dwa pola o tej samej strukturze współdzielą jedną strukturę zamiast tworzyć kopie.
Scalanie tablic i pola Option
Gdy przekazujesz tablicę obiektów, konwerter scala je klucz po kluczu. Klucz obecny w niektórych elementach, a w innych nieobecny, staje się 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 pojawia się w każdym elemencie, więc pozostaje wymagane. nick brakuje przy drugim użytkowniku, więc staje się Option<String>. Zwróć uwagę na to, czego tu nie ma: brak #[serde(default)]. serde już traktuje Option jako opcjonalne i deserializuje brakujący klucz jako None, więc atrybut byłby zbędny.
Poprawne typowanie liczb: i64, u64, f64
To reguła, którą konkurencyjne narzędzia pomijają, i to właśnie ona zawodzi na rzeczywistych payloadach. Generator stosuje trzy poziomy. Liczba całkowita mapuje się na i64. Jeśli wartość przekracza i64::MAX, awansuje do u64. Powyżej u64::MAX cofa się do f64. Każdy token zapisany z kropką dziesiętną lub wykładnikiem, jak 1.0 czy 2e3, mapuje się na f64 niezależnie od wielkości.
{ "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,
}
Tutaj user_id jest większe niż i64::MAX, więc trafia do u64 i nadal poprawnie przechodzi konwersję w obie strony. balance ma kropkę dziesiętną, więc jest f64, a nie liczbą całkowitą. To istotne, bo serde wymusza typ przy deserializacji. Podaj mu strukturę, która typuje identyfikator w stylu snowflake lub Twittera jako i32, a wartość się przepełni; wpisz pole zmiennoprzecinkowe jako i64, a serde zwróci błąd zamiast po cichu obciąć wartość. To właśnie reguła trzech poziomów sprawia, że wygenerowany rust struct from json deserializuje się, zamiast panikować przy pierwszym dużym identyfikatorze.
Pola snake_case i #[serde(rename)]
Klucze JSON są często w camelCase; idiomatyczne pola Rust są w snake_case. Konwerter zmienia nazwę pola i dodaje #[serde(rename)], który mapuje ją z powrotem na dokładny klucz JSON:
{ "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,
}
Klucz, który już jest w snake_case, jak login, nie dostaje rename. Pozostałe mapują się czysto z powrotem na format przesyłany po sieci, a kod w Rust czyta się naturalnie.
#[serde(rename)] a #[serde(rename_all)]
Narzędzie generuje #[serde(rename)] dla każdego pola z osobna, bo to zawsze działa — nawet gdy jeden payload miesza camelCase, snake_case i nieregularne klucze w tym samym obiekcie. Nigdy nie musi rozstrzygać o wspólnej konwencji, która może nie istnieć.
Gdy każde pole w strukturze rzeczywiście dzieli jedną konwencję, można zwinąć te atrybuty w pojedynczy rename na poziomie kontenera. Usuń wiersze per pole i umieść jeden #[serde(rename_all = "camelCase")] na strukturze:
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,
}
Obie wersje deserializują ten sam JSON. Używaj rename per pole przy mieszanych lub nieregularnych kluczach; sięgaj po rename_all, gdy struktura jest jednorodna i zależy ci na mniejszym szumie. serde dostarcza też inne konwencje dla tego samego atrybutu, takie jak "snake_case", "kebab-case" i "SCREAMING_SNAKE_CASE".
Słowa kluczowe Rust i klucze niebędące identyfikatorami
Klucze JSON to zwykłe stringi, więc nic nie powstrzyma API przed wysłaniem klucza o nazwie type lub match — oba są słowami zarezerwowanymi w Rust. Generator sanityzuje je do legalnego identyfikatora plus 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,
}
Forma z końcowym podkreśleniem (type_) jest celowo wybierana zamiast surowego identyfikatora w rodzaju r#type. Surowe identyfikatory nie potrafią wyrazić każdego słowa kluczowego w każdej pozycji; w szczególności self, crate i super są odrzucane jako surowe identyfikatory, więc podejście sanityzacja-plus-rename jest jedynym, które zawsze się kompiluje. Klucze niebędące identyfikatorami, takie jak first-name czy zaczynające się od cyfry 2fa, są traktowane tak samo: poprawna nazwa Rust i rename z powrotem na dosłowny klucz JSON.
Deserializacja za pomocą serde_json
Gdy masz już strukturę, dodaj zależności do Cargo.toml:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Parsowanie to potem jedna linia. Ten przykład jest kompletny i kompiluje się bez zmian:
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(())
}
Makro derive Deserialize wykonuje całą pracę. Użyj serde_json::from_str dla stringa, from_slice dla wycinka bajtów, from_reader dla pliku lub ciała HTTP, a serde_json::to_string, aby zserializować wartość z powrotem do JSON. To nagroda za poprawne dobranie typów: wygenerowany serde derive jest walidatorem czasu wykonania, więc wywołanie serde_json deserialize, które zwraca Ok, to gwarancja, że dane pasowały do twojej struktury — a nie tylko przekonanie kompilatora, że tak było.
Daty, dynamiczne klucze i nieznane pola
Niektóre kształty nie mapują się na zwykłe pole struktury, więc generator daje im rozsądny domyślny wynik, który potem dopracowujesz ręcznie.
Daty. JSON nie ma typu daty, więc znacznik czasu ISO lub RFC 3339 przychodzi jako zwykły String. Do prawdziwej obsługi dat zmień pole na typ chrono i włącz funkcję serde biblioteki chrono (chrono = { version = "0.4", features = ["serde"] }). serde parsuje wtedy RFC 3339 automatycznie:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub name: String,
pub created_at: DateTime<Utc>,
}
Dynamiczne klucze. Gdy klucze zmieniają się w czasie działania, jak mapa identyfikatorów na wartości, sztywna struktura to zły kształt. Użyj HashMap z kluczem String:
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub scores: HashMap<String, i64>,
}
Nieznane dodatkowe pola. Aby zachować typowaną strukturę, a mimo to wychwycić klucze, których nie zamodelowałeś, dodaj pole #[serde(flatten)] oparte na HashMap. Dla wartości naprawdę dynamicznej serde_json::Value jest uniwersalnym rozwiązaniem. Gdy tak naprawdę potrzebujesz walidować strukturę, a nie generować typy, przewodnik po walidacji JSON Schema omawia egzekwowanie kontraktu od początku do końca.
json-to-rust a quicktype, typeshare i pisanie ręczne
Nie ma jednego najlepszego sposobu na wygenerowanie typów Rust z JSON. Zależy to od tego, gdzie znajduje się JSON i w którą stronę konwertujesz.
| Podejście | Najlepsze do | Uwaga |
|---|---|---|
| Konwerter online (to narzędzie, transform.tools) | Jednorazowe konwersje, wrażliwe payloady, zero instalacji | Wnioskuje z próbki, w całości po stronie klienta |
| quicktype | Wyjście wielojęzyczne, generowanie kodu w potoku | Też sterowane próbką; CLI lub web |
| typeshare | Współdzielenie istniejących typów Rust z TS, Swift, Kotlin | Odwrotny kierunek: Rust do innych języków |
| schemafy / typify | Generowanie z JSON Schema | Wejściem jest schemat, nie próbka |
| Pisanie struktur ręcznie | Malutkie payloady, nauka serde | Pełna kontrola, żmudne i podatne na błędy przy skali |
Rozróżnienie warte zapamiętania: konwerter online i quicktype oba wnioskują typy z próbki JSON, podczas gdy typeshare działa w drugą stronę, zamieniając adnotowane typy Rust na TypeScript lub Swift. schemafy i typify przyjmują na wejściu JSON Schema, a nie przykładowe dane. Jeśli twoja baza kodu jest w TypeScripcie, a nie w Rust, to samo podejście sterowane próbką stosuje Konwerter JSON na TypeScript, a przewodnik po interfejsach JSON na TypeScript omawia tę stronę dogłębnie.
Typowe pułapki przy generowaniu Rust z JSON
Wygenerowane struktury to punkt wyjścia. Zwróć uwagę na te kwestie, zanim zaufasz wynikowi na żywych danych.
- Pojedyncza próbka rzadko ujawnia każdy kształt. Pola opcjonalne można wywnioskować tylko z wielu elementów tablicy. Wklej reprezentatywną tablicę, aby wnioskowanie
Optionbyło trafne, a nie zgadywaniem z jednego szczęśliwego obiektu. - Puste lub mieszane tablice cofają się do
serde_json::Value. To uczciwa odpowiedź, gdy nie ma z czego wnioskować. Podaj bogatszą próbkę, aby uzyskać konkretny typ elementu. - Nie zawężaj dużego identyfikatora do
i32. Identyfikatory typu snowflake i podobne przekraczają 2^53 i przepełniają 32-bitowe pole. Zachowaj wygenerowanyi64lubu64. - Pole zawsze
nullstaje sięOption<serde_json::Value>, a nie zwykłym opcjonalnym. Narzędzie nie ma typu do wywnioskowania, więc potraktuj je jako miejsce zastępcze i nadaj mu prawdziwy typ, gdy go poznasz. serde_json::Valuewymaga zależnościserde_json. Jeśli wynik zawieraValue, a wCargo.tomlbrakuje tego crate’a, kod się nie skompiluje.- Data pozostawiona jako
Stringnie wykona obliczeń na datach. Jeśli planujesz porównywać lub formatować znaczniki czasu, przejdź na typ chrono zamiast parsować stringi ręcznie.
Najczęściej zadawane pytania
Dlaczego serde odrzuca mój JSON, choć struktura wygląda poprawnie?
Prawie zawsze to niezgodność typów. Dwie typowe przyczyny to wartość zmiennoprzecinkowa lądująca w polu i64 oraz klucz, który bywa nieobecny, ale nie jest otypowany jako Option. serde wymusza typ przy deserializacji, więc popraw pole na f64 lub Option<T>, albo wygeneruj strukturę ponownie z reprezentatywnej próbki.
Jaki typ liczbowy Rust powinna przyjąć liczba całkowita JSON?
Domyślnie i64. Awansuj do u64, gdy wartość przekracza i64::MAX, i cofnij się do f64 powyżej u64::MAX. Każda liczba zapisana z kropką dziesiętną lub wykładnikiem mapuje się na f64 niezależnie od wielkości, bo serde odrzuca liczbę zmiennoprzecinkową deserializowaną do pola całkowitego.
Jak sprawić, by pole było opcjonalne, gdy JSON je pomija?
Otypuj je jako Option<T>. serde automatycznie traktuje Option jako opcjonalne i deserializuje brakujący klucz jako None, więc nie potrzebujesz #[serde(default)]. Konwerter oznacza pole jako Option, gdy niektóre elementy próbki nie mają tego klucza.
Czy używać #[serde(rename)] czy #[serde(rename_all)]?
Używaj #[serde(rename)] per pole, gdy payload miesza style nazewnictwa; to zawsze działa. Jeśli każde pole w strukturze podąża za jedną konwencją, usuń atrybuty per pole i umieść zamiast nich pojedynczy #[serde(rename_all = "camelCase")] na strukturze. Oba deserializują się identycznie.
Jak obsłużyć klucze JSON będące słowami kluczowymi Rust, jak type?
Generator emituje type_ z #[serde(rename = "type")], który mapuje z powrotem na pierwotny klucz. To bardziej niezawodne niż surowy identyfikator w rodzaju r#type, bo surowe identyfikatory nie obejmują self, crate i super w każdej pozycji.
Dlaczego daty JSON są typowane jako String zamiast typu daty?
JSON nie ma typu daty, więc znacznik czasu to po sieci zwykły string, a String to uczciwy domyślny wybór. Do prawdziwej obsługi dat zmień pole na DateTime<Utc> lub NaiveDate z chrono i włącz funkcję serde biblioteki chrono; serde parsuje wtedy RFC 3339 za ciebie.
Jak zdeserializować JSON do wygenerowanej struktury?
Dodaj serde_json do Cargo.toml, a następnie napisz let root: Root = serde_json::from_str(json)?;. Makro derive Deserialize zajmie się resztą. Użyj from_slice dla wycinka bajtów i from_reader dla pliku lub ciała HTTP.
Czy mój JSON jest prywatny przy korzystaniu z internetowego konwertera JSON na strukturę Rust?
Tak. Konwersja odbywa się w 100% w przeglądarce dzięki JavaScript. JSON, w tym tokeny, identyfikatory i dane klientów, nigdy nie opuszcza strony i nigdy nie jest wysyłany na serwer. Gdy będziesz gotowy, wypróbuj Konwerter JSON na struktury Rust na własnym payloadzie.