要把 Rust 結構體轉成 JSON 字串,可在型別上衍生 Serialize,然後在值上呼叫 serde_json::to_string 或 serde_json::to_string_pretty——回傳的 String 就是 JSON payload。反向——從一段 JSON 樣本生成結構體,以便日後再序列化回文字——是多數開發人員卡住的地方,特別是當鍵來自外部 API 而採用 camelCase、同一個欄位出現混合型別、或數字超過 i64 的時候。JSON to Rust Struct Converter 正是針對這個啟動步驟,將一段嚴格的 JSON 樣本轉成可審閱的 Rust 原始碼,並附上 Serialize、Deserialize 與 Debug 的衍生,實際的序列化呼叫則留給你的專案去執行。原始碼產生在本機執行,頁面只輸出文字,而目標的 Cargo.toml 預期會固定相容版本的 serde 與 serde_json。完成後,value → JSON → value 這樣的往返流程,就成為用來檢驗推論出的型別是否真實符合你生產資料的測試。

把 Rust 結構體序列化為 JSON 實際上做了什麼
序列化就是走訪一個 Rust 值,並將每個欄位寫成 JSON 文字。Serde 透過兩個半部來實作這個走訪流程:一個知道如何序列化自己的資料結構(Serialize trait),以及一個知道如何消化這些呼叫的資料格式(serde_json crate)。對於常見的結構體轉字串案例,管線相當精簡:在型別加上 #[derive(Serialize)],建構一個值,然後呼叫 serde_json::to_string(&value)。函式庫會處理欄位命名、容器寫入與數字格式化,並回傳一個 String。當你需要用於記錄或除錯的漂亮輸出時,serde_json::to_string_pretty 會加上兩個空格的縮排,但不改變資料模型。
反向——把那段 JSON 文字再轉回結構體——會用到 #[derive(Deserialize)] 與 serde_json::from_str::<MyStruct>(&text)。兩個方向共用同一個型別上產生的 impl,這也是為什麼手寫出來的結構體,也可以透過序列化它、再把結果解析回來的方式手動測試。任何從 JSON 樣本產生的型別,都會用同一個往返模式來驗證。
為什麼結構體前面會擺一段 JSON 樣本
大多數實際運作的結構體並不是從 Rust 程式碼開始的。它們是從某個上游服務吐出的 JSON 開始的,Rust 型別是從那段樣本反向工程出來的。挑戰在於,單一樣本並不是 API 合約:今天存在的欄位,明天可能就消失;今天能塞進 i64 的整數,下一季可能就會超過;看起來像數字的字串,實際上可能是 UUID。對單一樣本手寫型別,等於默默地把這些假設寫死。JSON to Rust Struct Converter 透過輸出 Option 欄位、serde_json::Value 後備型別,以及 serde rename 屬性,讓這些假設變得明確——每一個都是給開發人員審閱用的佔位符。
推論出來的原始碼並不是最終合約。它是一個起點,記錄了樣本中的證據,所以剩下唯一要決定的是哪些欄位要放寬(u64、i128、Decimal、字串),以及哪些要當作 enum、借用切片,或加標籤的 union。一旦審閱完成,把結構體序列化為 JSON 字串就和先前同一段程式碼路徑——derive 巨集與 to_string 呼叫並不在意這個型別是怎麼寫出來的。
產生可審閱的型別,再把它們序列化為 JSON 字串
- 把一段具有代表性的嚴格 JSON 樣本貼到頁面。物件、陣列或基本型別都可以;若是物件,根結構體會以你提供的輸入命名。
- 輸入一個 ASCII 根型別名稱(例如 Config 或 Invoice)。產生器會依物件路徑建立巢狀型別名稱,因此不需要其他命名輸入。
- 產生原始碼並逐一檢視工具所做的決定:數字寬度、Option 封裝、為混合證據準備的 serde_json::Value 後備型別、為 camelCase 或含連字號鍵準備的 serde renames,以及 Rust 關鍵字後方的底線。
- 把產生出來的原始碼複製到一個 Rust 專案,其 Cargo.toml 已經固定了相容且啟用 derive feature 的 serde,以及 serde_json。這個頁面不會幫你加入相依套件。
- 在複製進來的檔案上執行 cargo fmt,然後執行 cargo build。編譯錯誤通常指向缺少的 import(use serde::{Serialize, Deserialize};)或 crate 版本不符——這兩者都屬於專案端,而非產生器端。
- 以 serde_json::from_str::<YourType>(&fixture) 搭配 serde_json::to_string(&value) 對具代表性的 JSON 進行往返,並確認兩者皆成功。這是唯一能證明產生的型別符合你資料的測試。
轉換器如何把 JSON 證據對應到 Rust 型別
下表的對應規則就是產生器所套用的。它們刻意保守:只要樣本提供了衝突的證據,工具就降階到一個能表達不確定性的型別,而不是憑一段 JSON 文件就去虛構它無法證明的精確度。
| 樣本中的 JSON 證據 | 輸出的 Rust 型別 |
|---|---|
| 字串值 | String |
| 布林值 | bool |
| 位於 JavaScript 安全範圍內的整數 | i64 |
| 超出安全範圍或帶小數點的整數 | f64 |
| 物件 | 帶有 #[derive(Serialize, Deserialize, Debug)] 的公開結構體 |
| 元素型別合併過的陣列 | Vec<ElementType> |
| 空陣列 | Vec<serde_json::Value> |
| 單獨的 null | Option<serde_json::Value> |
| null 與型別為 T 的非空值同時出現 | Option<T> |
| 同一個欄位中出現不相容的混合值 | serde_json::Value |
對後續的「結構體轉 JSON 字串」呼叫而言,這帶來兩個重要結果。第一,型別為 Option<T> 的欄位會依選項是 Some 或 None,序列化為值或 JSON null——serde 會自動處理這個分支。第二,那些被推論為 serde_json::Value 的欄位,會把你存放進去的任何 JSON 值序列化出去,這保留了原始證據,但也代表線上的形態完全取決於你放進去的內容。
Serde 重新命名、snake_case 欄位與識別碼衝突
JSON 的鍵不一定是合法的 Rust 識別碼,Rust 的欄位名也不一定是你想在線上呈現的名稱。產生器會解決這個不匹配的兩端。駝峰式邊界、空格、標點與連字號會變成底線;前導的數字會被加上前綴;Rust 關鍵字會加上尾端底線;而相互衝突的標準化識別碼會被加上數字後綴。每當 Rust 欄位名與原始 JSON 鍵不同,產生器就會輸出 #[serde(rename = "originalKey")] 屬性,讓反序列化仍能找到正確的線上欄位,而序列化時也能把原始鍵寫回去。
這就是一段會默默壞掉的產生型別,以及一段能成功往返的產生型別之間的差別。少了 rename 屬性時,像 createdAt 這樣的 JSON 鍵,只有在手動別名後才能反序列化進名為 created_at 的 Rust 欄位;有了這個屬性,serde 就會在讀取時從線上讀 createdAt,並在你呼叫 serde_json::to_string 時把 createdAt 寫回去。同樣的屬性也是讓後續「結構體轉 JSON 字串」步驟產生與原始 API(而非 Rust 化名版本)相符輸出的關鍵。
用真實 fixture 對產生的結構體做往返測試
單一樣本的推論無法建立 API 合約。請把產生的原始碼當作假設,並用涵蓋成功與失敗情境的 fixture 來測試。今天是 Option 的欄位,明天的回應中可能就是必填;一個小整數在數值成長後,可能需要 u64、i128、Decimal,或改用字串;一個字串可能代表 UUID、日期、URL、enum 變體、借用切片,或機密資料;而混合的物件可能值得用加標籤的 enum 來表示。請以至少一個符合預期形狀的 fixture,加上一個應該反序列化失敗的 fixture,執行 cargo test,並確認 to_string 的輸出與輸入完全一致(或至少語義上相同)。關於可用於這些精細調整的 trait 介面,serde 關於 derive feature 的文件有所涵蓋:Serde 的 using derive 頁面,以及更全面的 Serde overview。
Rust 端的識別碼規則記載於 doc.rust-lang.org/reference/identifiers.html;讀過之後就能理解為什麼產生器會為前導數字加前綴、為關鍵字加底線——這些是語言層級的限制,而非工具的怪癖。
嚴格 JSON、限制以及這個頁面不會做的事
產生器只接受嚴格的 JSON。註解、尾端逗號、NaN、Infinity、BigInt 語法、undefined,以及 JavaScript 物件實字,都會在推論執行前被拒絕。JSON 數字會先由 JavaScript 解析,因此不安全的整數拼法無法被精確表示,而會從 i64 推論降級;對精度敏感的識別碼與小數,應在來源合約中以字串形式保留。工作範圍上限為輸入字元數 500,000、值數 50,000、巢狀層級 40,以及輸出字元數 1,000,000。這個頁面不會編譯 Rust、不會安裝 crate、不會執行產生的程式碼,也不會上傳樣本,並且不會處理 crate 路徑、模組可見性策略、生命周期、泛型、自訂序列化器、deny_unknown_fields、預設值、扁平化、加標籤或驗證。
| 限制 | 數值 |
|---|---|
| 輸入字元數 | 500,000 |
| 走訪的值數 | 50,000 |
| 巢狀深度 | 40 |
| 輸出字元數 | 1,000,000 |
由於這個工具只輸出原始碼文字,相依套件由目標專案自行負責。請在 Cargo.toml 加入啟用 derive feature 的 serde,以及 serde_json,然後把產生的檔案貼到 src/,執行 cargo fmt 與 cargo build,接著撰寫上述的往返測試。從 JSON 樣本到一個你能安心序列化為 JSON 字串的結構體,generate、format、compile、round-trip 這套流程就是完整的工作流。
如果還在權衡各個選項,JSON Schema to Zod:Inference Limits and a Safer Path 有更詳細的說明。
如果還在權衡各個選項,Convert .properties Files to JSON Online 有更詳細的說明。