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に自身のデータを書き込んでいることがわかります。serializer
はserde::Serializer
というトレイトを実装しており、書き込まれたデータを特定のフォーマットへ変換する役割を担っています。つまり、先程構造体からjsonへの変換に用いたserde_json::to_string()
の中では次のような処理が行われています。
- json用のシリアライザ(
serde_json::Serializer
)を作成する。 - 引数として渡された
Config
に定義されたserialize()を実行し、構造体のデータを作成したシリアライザに渡す。 - シリアライザが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()
では、次の処理が行われています。
- JSON用のデシリアライザ(
serde_json::Deserializer
)を作成する。 - デシリアライザが文字列を読み込み、JSONを解釈する。
-
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)]
を追加するだけで、構造体や列挙体に実装することが可能です。
対応しているフォーマット
以下のリンクを参照して下さい。
https://docs.serde.rs/serde/#data-formats
属性
#[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を使っているうちにわからないことがあれば、ここに解決策が書かれていないか見ておきましょう。