Serde是一個Rust程式語言上的框架,可以有效率地讓某個結構能被序列化(Serialization)或是被反序列化(Deserialization),而且序列化或反序列化的程式只要撰寫一次,就可以被用在多種不同的資料格式(如JSON、MessagePack、BSON等),或是自行定義的資料格式上。



什麼是序列化?

序列化就是把某個存在於記憶體中的資料狀態保存下來的動作。例如:

struct Point {
    x: f64,
    y: f64,
}

let p1 = Point {
    x: 7.0,
    y: 8.0,
};

以上的p1變數所儲存的Point結構實體,可以被序列化成以下的JSON格式的文字來保存到檔案、資料庫,甚至是用來在不同的應用程式之間傳輸:

{
    "x": 7.0,
    "y": 8.0
}

而反序列化,顧名思義序列化的反向動作,把某個已經被序列化好的值再還原到記憶體中。例如以上的JSON格式的文字,可以再被反序列化為:

Point {
    x: 7.0,
    y: 8.0,
}

Serde框架

serde

Serde框架的套件(crate)名稱為serde,如果要用它提供的程序式巨集,就要啟用它的derive特色。

若要引用它們,可在Cargo.toml設定檔的[dependencies]區塊加上:

serde = { version = "*", features = ["derive"] }

巨集的使用

序列化

以剛才提到的Point結構體來舉例,若要將它加上序列化的功能,程式只要寫成以下這樣就好了:

use serde::Serialize;

#[derive(Serialize)]
struct Point {
    x: f64,
    y: f64,
}

在某個型別上的derive屬性加上Serialize參數,可以讓該型別自動去實作serde::Serialize特性。有關於這個特性的手動實作方式,由於十分繁瑣,本篇文章就不說明了。

型別中要跟著被序列化的資料,其型別也必須要有實作serde::Serialize特性才行。Serde框架已經有對所有Rust程式語言的基本資料型別(如u8i16f32等)、字串和字串切片、陣列(array)、元組(tuple)、單元(unit,即())、Option列舉(代表有無資料的存在)、VecHashMapBTreeMapPhantomData等型別實作了serde::Serialize特性。

反序列化

繼續用Point結構體來舉例,若要將它加上反序列化的功能,程式只要寫成以下這樣就好了:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

在某個型別上的derive屬性加上Deserialize參數,可以讓該型別自動去實作serde::Deserialize特性。有關於這個特性的手動實作方式,由於十分繁瑣,本篇文章就不說明了。

同樣地,型別中要跟著被反序列化的資料,其型別也必須要有實作serde::Deserialize特性才行。Serde框架已經有對其預設有實作serde::Serialize特性的型別(見上個小節)實作serde::Deserialize特性了。

舉個實例看看?

如果您覺得把型別加上#[derive(Serialize, Deserialize)]屬性後就可以讓它能進行序列化和反序列化這件事很抽象的話,可以試著將其序列化成某種您熟悉的資料格式來試試。

以JSON為例,在Cargo.toml設定檔的[dependencies]區塊再加上:

serde_json = "*"

然後撰寫如下的可執行程式:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0,
  "y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}

巨集的進階使用

在實作序列化和反序列化的程式時會遇到各式各樣的需求,底下會繼續說明Serde的程序式巨集能夠做到哪些常需要的功能。

序列化時忽略某個欄位,讓反序列化時的欄位有預設值

替想要在序列化時忽略的欄位加上#[serde(skip_serializing)]屬性,該欄位就不會被序列化。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip_serializing)]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    // let p2: Point = serde_json::from_str(&json).unwrap();

    // println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0
}

注意這個程式,我們並沒有去反序列化JSON格式的文字,因為JSON格式的文字中並不存在y欄位,所以會反序列化失敗!為了解決這個問題,我們也可以替y欄位的serde屬性加上default參數,使其在反序列化時,如果來源資料中並沒有y欄位,就去使用y欄位的Default特性來產生預設值。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip_serializing, default)]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0
}
Point {
    x: 7.0,
    y: 0.0,
}

如果想用某個指定函數來產生預設值,可以將default參數改成default = "path",其中的path是指要呼叫的函數的路徑。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip_serializing, default = "default_y")]
    y: f64,
}

fn default_y() -> f64 {
    5.0
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0
}
Point {
    x: 7.0,
    y: 5.0,
}
序列化時按照條件忽略某個欄位

替想要在序列化時忽略的欄位加上#[serde(skip_serializing)]屬性,該欄位就不會被序列化。但是若我們只是想要讓其在某個條件時不被序列化的話,就要使用skip_serializing_if = "path"參數,而不是skip_serializing,其中的path是指要呼叫的函數的路徑,這個函數必須要回傳一個bool值,來決定是否不要將其序列化。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip_serializing_if = "ignore_y", default = "default_y")]
    y: f64,
}

fn default_y() -> f64 {
    5.0
}

fn ignore_y(y: &f64) -> bool {
    *y == 8.0
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0
}
Point {
    x: 7.0,
    y: 5.0,
}

這個功能通常會用在欄位的型別是Option列舉或是集合時。

像是:

#[derive(Serialize, Deserialize)]
struct Student {
    id: String,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    toeic_score: Option<u16>,
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    awards: Vec<String>,
}
反序列化時忽略某個欄位

替想要在反序列化時忽略的欄位加上#[serde(skip_deserializing)]屬性,該欄位就不會被反序列化。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip_deserializing, default)]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0,
  "y": 8.0
}
Point {
    x: 7.0,
    y: 0.0,
}
序列化和反序列化時忽略某個欄位

替想要在反序列化時忽略的欄位加上#[serde(skip)]屬性,該欄位就不會被序列化和反序列化。這功能等同於使用#[serde(skip_serializing, skip_deserializing)]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    #[serde(skip, default)]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0
}
Point {
    x: 7.0,
    y: 0.0,
}
更改序列化出來的欄位名稱

替欄位加上#[serde(rename = "new_name")]屬性,該欄位序列化出來的欄位名稱就會被更改。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    #[serde(rename = "X")]
    x: f64,
    #[serde(rename = "Y")]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "X": 7.0,
  "Y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}

雖然比較不常用,但如果想要分別設定序列化時輸出的欄位名稱,和反序列化時輸入的欄位名稱,要改寫成#[serde(rename(serialize = "ser_name", deserialize = "de_name"))]

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    #[serde(rename(serialize = "X", deserialize = "a"))]
    x: f64,
    #[serde(rename(serialize = "Y", deserialize = "b"))]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str("{\"a\": 7.0, \"b\": 8.0}").unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "X": 7.0,
  "Y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}

如果我們想要統一修改輸入、輸出的欄位名稱的格式,比如全部小寫、全部大寫、駝峰式等等,可以替整個型別加上#[serde(rename_all = "rename_method")]屬性,其中的rename_method允許的值有以下幾種:

  • lowercase:小寫。
  • UPPERCASE:大寫。
  • PascalCase:第一個字母為大寫的駝峰式命名。這種是Rust的型別名稱建議使用的命名方式。
  • camelCase:第一個字母為小寫的駝峰式命名。
  • snake_case:小蛇式命名,用底線_連接小寫單字。這種是Rust的欄位名稱建議使用的命名方式。
  • SCREAMING_SNAKE_CASE:大蛇式命名,用底線_連接大寫單字。這種是Rust的常數或是靜態變數名稱建議使用的命名方式。
  • kebab-case:小串烤肉式命名,用減號-連接小寫單字。
  • SCREAMING-KEBAB-CASE:大串烤肉式命名,用減號-連接大寫單字。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str(&json).unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "X": 7.0,
  "Y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}

同樣地,雖然比較不常用,但如果想要分別統一設定序列化時輸出的欄位名稱,和反序列化時輸入的欄位名稱,要改寫成#[serde(rename_all(serialize = "ser_rename_method", deserialize = "de_rename_method"))]

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all(serialize = "UPPERCASE"))]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str("{\"x\": 7.0, \"y\": 8.0}").unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "X": 7.0,
  "Y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}
反序列化時允許使用多個不一樣的欄位名稱

替欄位加上一個或多個#[serde(alias = "new_alias_name")]屬性,在反序列化時就可以用多種不同的欄位名稱來對應到同一個欄位。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    #[serde(alias = "a")]
    x: f64,
    #[serde(alias = "b")]
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str("{\"a\": 7.0, \"b\": 8.0}").unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0,
  "y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}
反序列化時拒絕不認識的欄位

Serde框架在預設情況下進行反序列化時,如果資料來源有比目標的資料型別還要有更多的資料欄位,Serde框架會接受這樣的來源。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str("{\"x\": 7.0, \"y\": 8.0, \"z\": 9.0}").unwrap();

    println!("{p2:#?}");
}

執行結果如下:

{
  "x": 7.0,
  "y": 8.0
}
Point {
    x: 7.0,
    y: 8.0,
}

如果想要讓反序列化的資料來源的欄位不比目標的資料型別還多的話,就要在該型別上加上#[serde(deny_unknown_fields)]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p1 = Point {
        x: 7.0, y: 8.0
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Point = serde_json::from_str("{\"x\": 7.0, \"y\": 8.0, \"z\": 9.0}").unwrap();

    println!("{p2:#?}");
}

以上程式執行的時候會發生panic,因為在反序列化時,來源的資料多了一個z欄位。

平板化(flatten)欄位

當欄位的型別是別的結構體或是HashMapBTreeMap等多欄位結構的資料時,若想將它們的欄位直接加進目前這層結構體之下來進行序列化與反序列化,就要在該欄位上加上#[serde(flatten)]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

#[derive(Debug, Serialize, Deserialize)]
struct Size {
    width:  f64,
    height: f64,
}

#[derive(Debug, Serialize, Deserialize)]
struct Region {
    #[serde(flatten)]
    point: Point,
    #[serde(flatten)]
    size:  Size,
}

fn main() {
    let r1 = Region {
        point: Point {
            x: 7.0, y: 8.0
        },
        size:  Size {
            width: 100.0, height: 70.0
        },
    };

    let json = serde_json::to_string_pretty(&r1).unwrap();

    println!("{json}");

    let r2: Region = serde_json::from_str(&json).unwrap();

    println!("{r2:#?}");
}

執行結果如下:

{
  "x": 7.0,
  "y": 8.0,
  "width": 100.0,
  "height": 70.0
}
Region {
    point: Point {
        x: 7.0,
        y: 8.0,
    },
    size: Size {
        width: 100.0,
        height: 70.0,
    },
}
借用(borrowing)擁有權

有些型別,如Cow等可以儲存一個實體,或者儲存一個參考。預設進行反序列化處理時,會將反序列化的來源資料複製出新的實體來儲存。

例如:

use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Name<'a> {
    first_name: Cow<'a, str>,
    last_name:  Cow<'a, str>,
}

fn main() {
    let n1 = Name {
        first_name: Cow::Borrowed("Magic"), last_name: Cow::Borrowed("Len")
    };

    let json = serde_json::to_string_pretty(&n1).unwrap();

    println!("{json}");

    let n2: Name = serde_json::from_str(&json).unwrap();

    drop(json);

    println!("{n2:#?}");
}

注意以上程式第22行,我們將json變數所儲存的字串實體的擁有權拿走了,但是這個程式仍然可以成功編譯並且被正常執行。這是因為預設在反序列化時,Cow列舉的變體會是Owned,因此反序列化來源資料會被另外複製一份來儲存。

如果要避免複製,可以替這類型別的欄位加上#[serde(borrow)]屬性。

例如:

use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Name<'a> {
    #[serde(borrow)]
    first_name: Cow<'a, str>,
    #[serde(borrow)]
    last_name:  Cow<'a, str>,
}

fn main() {
    let n1 = Name {
        first_name: Cow::Borrowed("Magic"), last_name: Cow::Borrowed("Len")
    };

    let json = serde_json::to_string_pretty(&n1).unwrap();

    println!("{json}");

    let n2: Name = serde_json::from_str(&json).unwrap();

    // drop(json);

    println!("Borrowed? {}", matches!(n2.first_name, Cow::Borrowed(_)));

    println!("{n2:#?}");
}

執行結果如下:

{
  "first_name": "Magic",
  "last_name": "Len"
}
Borrowed? true
Name {
    first_name: "Magic",
    last_name: "Len",
}

另外,#[serde(borrow)]屬性有個地方要注意的是,當要反序列化的欄位的型別內有使用#[serde(borrow)]屬性時,這個欄位也必須要加上#[serde(borrow)]屬性才能夠正常編譯。

例如:

use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Name<'a, 'b> {
    #[serde(borrow)]
    first_name: Cow<'a, str>,
    last_name:  Cow<'b, str>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Person<'a, 'b, 'c> {
    #[serde(borrow)]
    name:      Name<'a, 'b>,
    nick_name: Cow<'c, str>,
}

fn main() {
    let p1 = Person {
        name:      Name {
            first_name: Cow::Borrowed("Magic"), last_name: Cow::Borrowed("Len")
        },
        nick_name: Cow::Borrowed("Magic Len"),
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Person = serde_json::from_str(&json).unwrap();

    println!("Is first_name borrowed? {}", matches!(p2.name.first_name, Cow::Borrowed(_)));

    println!("Is last_name borrowed? {}", matches!(p2.name.last_name, Cow::Borrowed(_)));

    println!("Is nick_name borrowed? {}", matches!(p2.nick_name, Cow::Borrowed(_)));

    println!("{p2:#?}");
}

執行結果如下:

{
  "name": {
    "first_name": "Magic",
    "last_name": "Len"
  },
  "nick_name": "Magic Len"
}
Is first_name borrowed? true
Is last_name borrowed? false
Is nick_name borrowed? false
Person {
    name: Name {
        first_name: "Magic",
        last_name: "Len",
    },
    nick_name: "Magic Len",
}

不過以上程式會還有個比較好的寫法,因為Name結構體的last_name欄位實際上並不會去進行借用,但是如果直接在Personname欄位上加上#[serde(borrow)]屬性,'b的生命周期也會跟著被綁定上去。為了避免這個狀況造成問題,最好手動指定泛型生命周期參數給borrow參數。

如下:

use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Name<'a, 'b> {
    #[serde(borrow)]
    first_name: Cow<'a, str>,
    last_name:  Cow<'b, str>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Person<'a, 'b, 'c> {
    #[serde(borrow = "'a")]
    name:      Name<'a, 'b>,
    nick_name: Cow<'c, str>,
}

fn main() {
    let p1 = Person {
        name:      Name {
            first_name: Cow::Borrowed("Magic"), last_name: Cow::Borrowed("Len")
        },
        nick_name: Cow::Borrowed("Magic Len"),
    };

    let json = serde_json::to_string_pretty(&p1).unwrap();

    println!("{json}");

    let p2: Person = serde_json::from_str(&json).unwrap();

    println!("Is first_name borrowed? {}", matches!(p2.name.first_name, Cow::Borrowed(_)));

    println!("Is last_name borrowed? {}", matches!(p2.name.last_name, Cow::Borrowed(_)));

    println!("Is nick_name borrowed? {}", matches!(p2.nick_name, Cow::Borrowed(_)));

    println!("{p2:#?}");
}
FromInto特性

FromInto是Rust程式語言內建的兩個常用特性,可以快速地將一個型別的實體轉成另一個型別的實體。如果一個有加上Serialize屬性或是Deserialize屬性的型別,有實作From<FromType>Into<IntoType>特性,我們可以替其加上#[serde(from = "FromType")]#[serde(into = "IntoType")]屬性來間接去使用FromType或是IntoType實作的序列化或是反序列化的方式。

例如:

use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Name<'a, 'b> {
    first_name: Cow<'a, str>,
    last_name:  Cow<'b, str>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(into = "Name")]
struct Person<'a, 'b, 'c> {
    name:      Name<'a, 'b>,
    nick_name: Cow<'c, str>,
}

impl<'a, 'b, 'c> From<Person<'a, 'b, 'c>> for Name<'a, 'b> {
    fn from(val: Person<'a, 'b, 'c>) -> Self {
        val.name
    }
}

fn main() {
    let p = Person {
        name:      Name {
            first_name: Cow::Borrowed("Magic"), last_name: Cow::Borrowed("Len")
        },
        nick_name: Cow::Borrowed("Magic Len"),
    };

    let json = serde_json::to_string_pretty(&p).unwrap();

    println!("{json}");

    let n: Name = serde_json::from_str(&json).unwrap();

    println!("{n:#?}");
}

執行結果如下:

{
  "first_name": "Magic",
  "last_name": "Len"
}
Name {
    first_name: "Magic",
    last_name: "Len",
}

類單元結構體(unit-like struct)和列舉

上面都是用一般結構體來做序列化與反序列化的動作,那如果是類單元結構體或是列舉呢?

類單元結構體的例子如下:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct UnitStruct;

fn main() {
    let json = serde_json::to_string_pretty(&UnitStruct).unwrap();

    println!("{json}");

    let n: UnitStruct = serde_json::from_str(&json).unwrap();

    println!("{n:#?}");
}

執行結果如下:

null
UnitStruct

所以類單元結構體其實就等於沒有資料的意思?並不是,以上例子只是表示在JSON格式中,它代表null值而已。類單元結構體在Serde框架中還是有其被應用的地方,通常是用來直接以類單元結構體的名稱來表示某個名稱。

而列舉會有比較多需要說明的東西,例子如下:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
enum Week {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday,
}

fn main() {
    let json = serde_json::to_string_pretty(&Week::Friday).unwrap();

    println!("{json}");

    let w: Week = serde_json::from_str(&json).unwrap();

    println!("{w:#?}");
}

執行結果如下:

"Friday"
Friday

列舉的單元變體(Unit Variant),在Serde框架中通常會直接以變體的名稱來表示某個名稱。如果我們要將變體名稱存到某個欄位的話,可以替列舉加上#[serde(tag = "field")]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "WeekDay")]
enum Week {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday,
}

fn main() {
    let json = serde_json::to_string_pretty(&Week::Friday).unwrap();

    println!("{json}");

    let w: Week = serde_json::from_str(&json).unwrap();

    println!("{w:#?}");
}

執行結果如下:

{
  "WeekDay": "Friday"
}
Friday

接著來看看列舉的變體非單元變體時的情況。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
enum IP {
    IPv4(String),
    IPv6(String),
}

fn main() {
    let json = serde_json::to_string_pretty(&IP::IPv4("110.26.73.83".to_string())).unwrap();

    println!("{json}");

    let ip: IP = serde_json::from_str(&json).unwrap();

    println!("{ip:#?}");
}

執行結果如下:

{
  "IPv4": "110.26.73.83"
}
IPv4(
    "110.26.73.83",
)

此時的變體名稱就會變成欄位名稱,如果不想要讓列舉用變體名稱當欄位名稱,可以替列舉加上#[serde(untagged)]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum IP {
    IPv4(String),
    IPv6(String),
}

fn main() {
    let json = serde_json::to_string_pretty(&IP::IPv6("::ffff:110.26.73.83".to_string())).unwrap();

    println!("{json}");

    let ip: IP = serde_json::from_str(&json).unwrap();

    println!("{ip:#?}");
}

執行結果如下:

"::ffff:110.26.73.83"
IPv4(
    "::ffff:110.26.73.83",
)

使用這個#[serde(untagged)]屬性時要特別注意在反序列化時變體的匹配,Serde框架會選擇使用最先匹配成功的變體。像是上面這個例子,雖然我們使用IPv6變體來做序列化,但反序列化出來的變體為IPv4

我們也可以把列舉的變體名稱和變體的結構分兩個欄位來儲存,只要替列舉加上#[serde(tag = "variant_name_field", content = "variant_field")]屬性。

例如:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "ip")]
enum IP {
    IPv4(String),
    IPv6(String),
}

fn main() {
    let json = serde_json::to_string_pretty(&IP::IPv6("::ffff:110.26.73.83".to_string())).unwrap();

    println!("{json}");

    let ip: IP = serde_json::from_str(&json).unwrap();

    println!("{ip:#?}");
}

執行結果如下:

{
  "type": "IPv6",
  "ip": "::ffff:110.26.73.83"
}
IPv6(
    "::ffff:110.26.73.83",
)