LoginSignup
89
48

More than 5 years have passed since last update.

RustのSerdeの簡単な紹介

Posted at

serdeはRustのデータ構造をシリアライズ/デシリアライズするためのフレームワークです。これで何ができるか大雑把に紹介すると、Rustの構造体等のデータ構造を、自動的にJSONやTOML、YAML等のフォーマットと相互変換ができるようになるというものです。本記事ではこの非常に便利なserdeについて簡単に紹介します。

JSON文字列と構造体の相互変換

簡単な例として、Rustの構造体とJSON文字列の相互変換を扱います。アプリケーションの各種設定を格納するConfig構造体があり、この内容をJSONと変換したい時、以下のように記述します。

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Serialize, Deserialize, Debug)]
struct Config {
    user: String,
    number: u32,
}

fn main() {
    // この構造体をJSONに変換する
    let config = Config {
        user: "Hanako".to_owned(),
        number: 42
    };
    let json_str = serde_json::to_string(&config).unwrap();
    println!("Serialized Json = {}", json_str);

    // このJSON文字列をConfigに変換する
    let json_str = r#"
        {
            "user": "Taro",
            "number": 33
        }
    "#;
    let config: Config = serde_json::from_str(json_str).unwrap();
    println!("Deserialized Config = {:?}", config);
}

実行結果は次のようになります。

Serialized Json = {"user":"Hanako","number":42}
Deserialized Config = Config { user: "Taro", number: 33 }

Config#[derive(Serialize, Deserialize)]を追加するだけで、簡単にJSONとの相互変換ができるようになりました。ここで用いているserde_json::to_string()Serializeを実装した型を受け取り、Stringを返す関数です。また、serde_json::from_str()は文字列を受け取り、Serializeを実装した型を返す関数です(正確にはこれらの関数はResult型を返します)。これらの関数を使うことでRustのデータとJSON間の変換が行えます。

ちなみにこの例では、serde_json::from_str()を用いて文字列から読み込んでいますが、serde_json::from_reader()を用いれば、Readを実装した任意の型から読み込めます。そのためファイルなどから読み込むことが可能です。

serdeの仕組み

なぜ構造体に#[derive(Serialize, Deserialize)]を追加するだけでJSONとの相互変換ができるようになるのか、以下で簡単に説明していきます。

Serializeはトレイトであり、その定義は以下のようになっています。

pub trait Serialize {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer;
}

先程の例では、#[derive(Serialize)]によりConfigに次のような実装がされます。

impl Serialize for Config {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut s = serializer.serialize_struct("Config", 2)?;
        s.serialize_field("user", &self.user)?;
        s.serialize_field("number", &self.number)?;
        s.end()
    }
}

この実装を見ると、引数のserializerに自身のデータを書き込んでいることがわかります。serializerserde::Serializerというトレイトを実装しており、書き込まれたデータを特定のフォーマットへ変換する役割を担っています。つまり、先程構造体からjsonへの変換に用いたserde_json::to_string()の中では次のような処理が行われています。

  1. json用のシリアライザ(serde_json::Serializer)を作成する。
  2. 引数として渡されたConfigに定義されたserialize()を実行し、構造体のデータを作成したシリアライザに渡す。
  3. シリアライザがjsonに変換した結果をバッファに書き出す。

この手順により、構造体からJSONへの変換(シリアライズ)が行われます。

逆にJSONから構造体の変換は、Deseriazlieトレイトにて定義されるdeserialize()と、serde::Deserializerトレイトを実装したデシリアライザによって行われます。Deserializeトレイトの定義は次のようになっています。

pub trait Deserialize<'de>: Sized {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>;
}

deserialize()は、deserializerからデータを受け取って自身の値(Self)を生成します。そして、先程用いたserde_json::from_str()では、次の処理が行われています。

  1. JSON用のデシリアライザ(serde_json::Deserializer)を作成する。
  2. デシリアライザが文字列を読み込み、JSONを解釈する。
  3. Configに定義されたdeserialize()を実行し、デシリアライザから必要なデータを受け取ってConfigの値を作成する。

JSONの解釈はserde_jsonが提供するシリアライザ/デシリアライザに任せられており、SerializeとDeserializeはフォーマットの仕様からは独立です。そのため、serde_json以外のシリアライザ/デシリアライザを用いることにより、JSON以外のフォーマット(TOMLやYAML、MessagePackなど)に対応することができます。このことはserdeを強力なフレームワークにしている理由の1つだと思います。

Serialize/Deserializeを実装している型

i32やf64などの数値型、Stringなどの文字列型、そしてVecなどのコレクション型など、Rustの型のうち需要のありそうな型はたいていSerialize/Deserializeを実装しています。また、#[derive(Serialize, Deserialize)]を追加するだけで、構造体や列挙体に実装することが可能です。

対応しているフォーマット

以下のリンクを参照して下さい。

属性

#[derive(Serialize, Deserialize)]によるデフォルトの実装では、思ったように動作しない場合があります。とはいえ、自前で実装するとなると大変煩雑になるので、serdeの用意してくれる属性で目的が達成できる場合はこちらを利用しましょう。

例えば、先程のConfigにおいて、JSONでnumberが指定されていない時は、デフォルト値(Defaultトレイトにより定義される)が入るようにしてみます。#[serde(default)]という属性を追加するだけです。

#[derive(Serialize, Deserialize, Debug)]
struct Config {
    user: String,
    #[serde(default)]
    number: u32,
}

その他の属性についてはhttps://serde.rs/attributes.htmlを参照して下さい。

参考になるドキュメント

  • https://serde.rs/ serdeを使っているうちにわからないことがあれば、ここに解決策が書かれていないか見ておきましょう。
89
48
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
89
48