🔃

serdeに入門しよう!

に公開

serdeとは?

serdeはRust向けのシリアライゼーションライブラリです。
serialize + deserializeでser + de、合わせてserdeです。

serdeはシリアライズとデシリアライズを行う仕組みを提供し、実際にJSONやTOMLなどのフォーマットに変換するのはserde_jsonやtomlなどそれぞれのcrateが担当します。

本記事ではserde_jsonでJSONにシリアライズするとして説明します。
また、今回の例では簡単のため適切なエラーハンドリングは行わずunwrap()を使用しています。
実際に使用する場合は適切なエラーハンドリングを行うようにしてください。

基本的な使い方

まず、依存の導入をしましょう!

# serdeをderive featureを有効にして導入
cargo add serde --features derive

# JSONに変換するcrateであるserde_jsonを導入
cargo add serde_json

上記のコマンド実行により、Cargo.tomlは以下のようになります。
バージョンはタイミングによって変わります。

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"

serdeはderiveマクロとして使用することが一般的です。

deriveマクロ

deriveマクロはボイラープレート的な実装をマクロの力を使って代わりに実装させる機能です。
例えば、DefaultCloneなどのtraitは同名のderiveマクロを提供しています。

#[derive(Default, Clone)]
struct MyValue {
    value1: String,
    value2: i32,
}

上記のような型について考えます。
これを展開すると大体以下のようになります。

struct MyValue {
    value1: String,
    value2: i32,
}

impl Default for MyValue {
    fn default() -> Self {
        Self {
            value1: String::default(),
            value2: i32::default(),
        }
    }
}

impl Clone for MyValue {
    fn clone(&self) -> Self {
        Self {
            value1: self.value1.clone(),
            value2: self.value2.clone(),
        }
    }
}

このような何度も同じことを書かないといけない状態を避けるためderiveマクロを使用します。

SerializeとDeserialize

serdeにderive featureとともに依存に入れるとserde::Serializeserde::Deserializeというderiveマクロが使用可能になります。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyValue {
    value_one: String,
    value_two: i32,
}

上記をJSONにシリアライズする場合は以下のように行います

let value = MyValue { value_one: "v1".to_string(), value_two: 42 };
let json = serde_json::to_string(&value).unwrap();

この場合のjsonの値は以下のようになります。

{"value_one":"v1","value_two":42}

逆にデシリアライズする例は以下のようになります。

let json = r#"{"value_one":"v1","value_two":42}"#;
let value: MyValue = serde_json::from_str(json).unwrap();

これだけでも十分に使えますね。

名前を変えてみる

特定の項目名を変更する

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyValue {
    #[serde(rename = "renamed")]
    value_one: String,
    value_two: i32,
}

この場合は以下のようになります。

{"renamed":"v1","value_two":42}

非常に簡単ですね。

全部キャメルケース / ケバブケース ...に変える

serdeではデフォルトのキー名はフィールドと同じ名前になります。つまりRustの命名規則から基本的にはスネークケースになります。
ですが、すべてのフィールドで同じ命名規則を適用するのは大変です。そのための機能がserdeには備わっています。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MyValue {
    value_one: String,
    value_two: i32,
}

これをJSONにすると以下のようになります。

{"valueOne":"v1","valueTwo":42}

ここで使える命名法[1]の名前を以下に示します。

名前 どういう感じになるか
lowercase {"value_one":"v1","value_two":42}
UPPERCASE {"VALUE_ONE":"v1","VALUE_TWO":42}
PascalCase {"ValueOne":"v1","ValueTwo":42}
camelCase {"valueOne":"v1","valueTwo":42}
snake_case {"value_one":"v1","value_two":42}
SCREAMING_SNAKE_CASE {"VALUE_ONE":"v1","VALUE_TWO":42}
kebab-case {"value-one":"v1","value-two":42}
SCREAMING-KEBAB-CASE {"VALUE-ONE":"v1","VALUE-TWO":42}

デフォルトとスキップ

Optionとdefault

まず、以下のコードを見てください。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyValue {
    value_one: Option<String>,
    value_two: i32,
}

このコードはvalue_oneの型がOption<String>になっています。
serdeではOptionはnullableな値として扱われます。
上記のコードでvalue_oneNoneを渡してJSON化したものが以下です。

{"value_one":null,"value_two":42}

このように、nullとなっていますね。
Optionを使用することで、渡さない場合はnullにすることができます。

逆にデシリアライズのときはどうでしょうか?

まず、先程出力した以下のJSONをデシリアライズしてみましょう。

{"value_one":null,"value_two":42}

これを読み込むと以下のような値になります。

MyValue { value_one: None, value_two: 42 }

次に、nullの項目を省略し以下のJSONをデシリアライズしてみましょう。

{"value_two":42}

デシリアライズするコードは以下です。

let json = r#"{"value_two":42}"#;
let value: MyValue = serde_json::from_str(&json).unwrap();

結果はnullを入れた場合と同様に以下になります。

MyValue { value_one: None, value_two: 42 }

デシリアライズした際にデフォルトの値を決めたいこともあると思います。
例えば以下のような設定ファイルの場合です。

{
    "listen": "127.0.0.1:8080"
}

この設定ファイルはサーバーのホスト名とポートを指定する設定ファイルです。
多くのサーバーではデフォルトで使用するIPやポートが決まっています。
その場合は以下のように記述します。

#[derive(Serialize, Deserialize, Debug)]
struct Config {
    #[serde(default = "default_listen")]
    listen: String,
}

fn default_listen() -> String {
    "127.0.0.1:8080".to_string()
}

デフォルトの値を指定する場合は#[serde(default)]を使用します。
今回は、=でデフォルトの値を作る関数を指定していますが、省略することもできます。
省略した場合、Default::defaultが使用されます。
今回はdefault_listenという関数を作成し、それを使用しています。

これを使い以下のJSONをデシリアライズしてみましょう。

{}
Config { listen: "127.0.0.1:8080" }

skip

特定のフィールドをシリアライズやデシリアライズしないように指定することもできます。
シリアライズの際は、そのフィールドは項目として存在しなくなり、デシリアライズの際は、Default::default#[serde(default)]で指定した関数が使用されます。
以下の例を見てみましょう。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct SkipSerde {
    #[serde(skip)]
    skip1: String,
    #[serde(skip, default = "skip2_default")]
    skip2: String,
}

fn skip2_default() -> String {
    "skip2_default value".to_string()
}

これを、空のJSON ({}) でデシリアライズすると、以下の値になります。

SkipSerde { skip1: "", skip2: "skip2_default value" }

StringDefault::default()の値は空文字列なので、skip1の値は""となります。

また、#[serde(skip_serializing)]でシリアライズを行う際のみスキップしたり、#[serde(skip_deserializing)]でデシリアライズを行う際のみスキップすることもできます。

シリアライズの条件付きskip

シリアライズの際には条件付きでskipする機能もあります。
以下に例を示します。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct SkipSerde {
    #[serde(skip_serializing_if = "Vec::is_empty")]
    values: Vec<String>,
}

#[serde(skip_serializing_if = "Vec::is_empty")]の部分がその機能です。
この例ではVec::is_emptytrueの際にシリアライズがスキップされます。つまり、要素が空の場合はシリアライズされないことになります。
以下にそれを使用したコード例を示します。

fn main() {
    let value = SkipSerde { values: vec![] };
    let json = serde_json::to_string(&value).unwrap();
    println!("1: {json}");

    let value = SkipSerde { values: vec!["value".to_string()] };
    let json = serde_json::to_string(&value).unwrap();
    println!("2: {json}");
}

これの出力は以下のようになります。

1: {}
2: {"values":["value"]}

確かに要素がない場合は項目自体が出力されなくなりました。

enum

Rustには非常に強力なenum機能があります。
この強さはserdeでも同様に活かされます。

まず、普通にenumをシリアライズしてみましょう。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
enum EnumSerde {
    UnitStyle,
    TupleStyle(i32),
    StructStyle { value1: i32 },
}

fn main() {
    let value = EnumSerde::UnitStyle;
    let json = serde_json::to_string(&value).unwrap();
    println!("unit: {json}");

    let value = EnumSerde::TupleStyle(42);
    let json = serde_json::to_string(&value).unwrap();
    println!("tuple: {json}");

    let value = EnumSerde::StructStyle { value1: 42 };
    let json = serde_json::to_string(&value).unwrap();
    println!("struct: {json}");
}

これを実行すると以下のようになります。

unit: "UnitStyle"
tuple: {"TupleStyle":42}
struct: {"StructStyle":{"value1":42}}

tag

上記の通常通りのシリアライズはExternally taggedと呼ばれています。
StructStyleを見るとわかりやすいですが、"StructStyle"キーの値として、enumの値が入っています。つまり、項目名が外側 (External) にあるということです。

JSONでは値の内側に項目名を入れる形式の方が一般的ですし、パースもしやすいです。
その場合は以下のように修正します。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum EnumSerde {
    UnitStyle,
    // TupleStyle(i32),
    StructStyle { value1: i32 },
}

fn main() {
    let value = EnumSerde::UnitStyle;
    let json = serde_json::to_string(&value).unwrap();
    println!("unit: {json}");

    // let value = EnumSerde::TupleStyle(42);
    // let json = serde_json::to_string(&value).unwrap();
    // println!("tuple: {json}");

    let value = EnumSerde::StructStyle { value1: 42 };
    let json = serde_json::to_string(&value).unwrap();
    println!("struct: {json}");
}

この例では、#[serde(tag = "type")]をつけています。

unit: {"type":"UnitStyle"}
struct: {"type":"StructStyle","value1":42}

tuple形式では値の方の項目名がつけられないため、エラーになってしまいます。
tuple形式で使用する場合は、tagと一緒にcontentを指定します。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", content = "value")]
enum EnumSerde {
    TupleStyle(i32),
}

fn main() {
    let value = EnumSerde::TupleStyle(42);
    let json = serde_json::to_string(&value).unwrap();
    println!("tuple: {json}");
}

これを実行すると以下のようになります。

tuple: {"type":"TupleStyle","value":42}

untagged

逆に項目名を出力しないようにすることもできます。
それがuntaggedです。
以下のコードは、untaggedを使用した例です。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum EnumSerde {
    Variant1 { value: i32 },
    Variant2 { value: String },
}

fn main() {
    let value = EnumSerde::Variant1 { value: 42 };
    let json = serde_json::to_string(&value).unwrap();
    println!("JSON: {json}");

    let json = r#"{"value": "string value"}"#;
    let value: EnumSerde = serde_json::from_str(json).unwrap();
    println!("Value: {value:?}");
}

これの出力結果は以下のようになります。

JSON: {"value":42}
Value: Variant2 { value: "string value" }

このuntaggedはバリアントを識別するタグがないため、デシリアライズ時には定義されている順番に各バリアントを試していきます。
今回はフィールド名は一致していますが、i32Stringという型の違いで識別可能なため適切に選択できます。ただし、同じ構造のバリアントが複数ある場合は、最初にマッチしたものが選ばれるため注意が必要です。

flatten

ある構造体に別の構造を埋め込み平坦化する機能もあります。
これを行う場合には#[serde(flatten)]を使用します。

まずは、以下のコードを見てみましょう。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct StructContent {
    value_i32: i32,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum EnumContent {
    Variant { value: i32 },
}

#[derive(Serialize, Deserialize, Debug)]
struct StructSerde {
    flatten_struct: StructContent,
    flatten_enum: EnumContent,
}

fn main() {
    let value = StructSerde {
        flatten_enum: EnumContent::Variant { value: 42 },
        flatten_struct: StructContent { value_i32: 42 },
    };
    let json = serde_json::to_string(&value).unwrap();
    println!("JSON: {json}");
}

これは#[serde(flatten)]を使っていないパターンです。
これを実行すると以下のような結果になります。

JSON: {"flatten_struct":{"value_i32":42},"flatten_enum":{"type":"Variant","value":42}}

flatten_structflatten_enumなど各項目のキーが含まれていますね。
これらの項目に#[serde(flatten)]をつけてみましょう。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct StructContent {
    value_i32: i32,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum EnumContent {
    Variant { value: i32 },
}

#[derive(Serialize, Deserialize, Debug)]
struct StructSerde {
    #[serde(flatten)]
    flatten_struct: StructContent,
    #[serde(flatten)]
    flatten_enum: EnumContent,
}

fn main() {
    let value = StructSerde {
        flatten_enum: EnumContent::Variant { value: 42 },
        flatten_struct: StructContent { value_i32: 42 },
    };
    let json = serde_json::to_string(&value).unwrap();
    println!("JSON: {json}");
}

これを実行すると以下のようになります。

JSON: {"value_i32":42,"type":"Variant","value":42}

各キーが埋め込まれた形になりましたね。
serdeではこのように別のstructやenumのキーを埋め込む機能があります。

tomlで使う

serdeはシリアライズとデシリアライズを提供する機能です。
そのため、当然JSON以外でも使用できます。
今回は試しにtomlで使用してみましょう。

まずは依存を追加します。

cargo add toml

そして、serde_jsonの部分をtomlに変えます。

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct StructContent {
    value_i32: i32,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum EnumContent {
    Variant { value: i32 },
}

#[derive(Serialize, Deserialize, Debug)]
struct StructSerde {
    #[serde(flatten)]
    flatten_struct: StructContent,
    #[serde(flatten)]
    flatten_enum: EnumContent,
}

fn main() {
    let value = StructSerde {
        flatten_enum: EnumContent::Variant { value: 42 },
        flatten_struct: StructContent { value_i32: 42 },
    };
    let tml = toml::to_string(&value).unwrap();
    println!("{tml}");
}

println!や変数名もTOML向けに変更しました。
これを実行すると以下になります。

value_i32 = 42
type = "Variant"
value = 42

出力がJSONからTOMLに変わりましたね!
serdeではこのように簡単にシリアライズフォーマットを変えることが可能です。

今回はserde_jsontomlがたまたまto_stringという関数名でシリアライズする形式だったため、その部分の修正は必要ありませんでしたが、crateによってシリアライズやデシリアライズをするメソッド名は変わるため、使用するcrateのドキュメントを参照するようにしてください。

まとめ

Rustのserdeは非常に便利なシリアライズとデシリアライズの仕組みを提供するcrateです。
カスタムシリアライザなしで、いろいろな構造を作ることができるので非常に便利です。
Rustを使うならぜひ使ってみてください!

脚注
  1. https://serde.rs/container-attrs.html#rename_all ↩︎

Discussion