Skip to content
Powrót do bloga
Poradniki

JSON na strukturę Rust: generuj struktury serde (2026)

Konwertuj JSON na struktury Rust: typy liczbowe (i64/u64/f64), Option dla null, serde rename dla camelCase oraz pułapki, których należy unikać. Za darmo.

11 min czytania

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:

  1. 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.
  2. Dostrój wynik. Zmień nazwę głównej struktury, przełącz makra derive serde, dodaj Debug i Clone lub usuń widoczność pub, aby dopasować się do stylu swojego crate’a.
  3. 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ść JSONTyp Rust
"text"String
42i64 (duże wartości u64, powyżej f64)
3.14, 2e3f64
true / falsebool
nullOption<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ścieNajlepsze doUwaga
Konwerter online (to narzędzie, transform.tools)Jednorazowe konwersje, wrażliwe payloady, zero instalacjiWnioskuje z próbki, w całości po stronie klienta
quicktypeWyjście wielojęzyczne, generowanie kodu w potokuTeż sterowane próbką; CLI lub web
typeshareWspółdzielenie istniejących typów Rust z TS, Swift, KotlinOdwrotny kierunek: Rust do innych języków
schemafy / typifyGenerowanie z JSON SchemaWejściem jest schemat, nie próbka
Pisanie struktur ręcznieMalutkie payloady, nauka serdePeł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 Option był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 wygenerowany i64 lub u64.
  • Pole zawsze null staje 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::Value wymaga zależności serde_json. Jeśli wynik zawiera Value, a w Cargo.toml brakuje tego crate’a, kod się nie skompiluje.
  • Data pozostawiona jako String nie 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.

Tagi: rust json serde type-safety developer-tools

Powiązane artykuły

Zobacz wszystkie artykuły