serde::Deserializeを自分で書けるようになろう
目的
この記事の目的は、Rust userの大半が使ったことがあるcrate, serdeのDeserializetraitを自分で実装できるようにすることです。
Rustを使っていて、serdeもよく使っているけどあまり仕組みはわかってない人も多いのではないでしょうか。そういう方をターゲットとしてserdeの仕組みからわかりやすくDeserializeを実装する方法を解説しようと思います。
背景
最近、仕事で外部APIからResponseとして送られてくるJSONを構造体にデシリアライズする必要がありました。通常このような場合はserdeが提供してくれるderive macroで自動的に構造体にDeserializeを実装し、serde_jsonによってデシリアライズすることが可能です。
しかし、今回Responseから送られてくるデータ構造は以下のように様々なデータ型の要素で構成される配列でした。
※ RFCによるとJSONでは配列に異なるデータ型が入っても良いことになっています。
[
1499040000000, // time
"0.01634790", // value_1
"0.80000000", // value_2
]
目標は以下のような構造体にフィールドと値を対応させて上記データをマッピングすることなので、通常のderive macroによるDeserialize実装では配列のindex指定で操作はできないので、目標を達成できません。
struct Response {
pub time: u64,
pub value_1: f64,
pub value_2: f64,
}
そこで、Deserializeの実装を自分で行う必要がありました。
以下では、serdeの仕組みの解説から始めて、実際にResponse構造体に対してDeserializeを実装するところまで見ていきたいと思います。
なお、この記事ではDeserialize部分にのみ注目し、Serializeの実装に関しては言及しません。一般にDeserializeの実装のほうがSerializeの実装より複雑化しやすいのでDeserializeの内容がわかればSerializeの説明は不要と考えます。
※ serde_tupleというcrateが同じような機能を提供してくれているのですが、この一つの構造体のために依存crateを増やしたくなかったので、自力で実装することにしました。
serde とは
serdeはデータをserialize/deserializeするために必要なデータ構造の抽象化するcrateです。serdeはJSONやYAMLといった特定のデータフォーマットをserialize/deserializeする方法を知っているData formatsと呼ばれるグループ(serde_json, serde_yamlなど)と、特定のデータ構造をserialize/deserializeを方法を定義するData stricturesと呼ばれるグループ(上記のResponseのような具体的なデータ構造)2つの仲介するレイヤを提供します。
具体的には、serdeはSerde data modelと呼ばれるAPIを提供します。これはRustのデータ構造をserdeが定義した29個のデータ構造にマッピングするための方法を提供するAPIです。こうすることで、Data format <-> Serde data model, 構造体 <-> Serde data modelそれぞれを分けて定義できるようなるので、構造体定義者とData format定義者で責任の境界がはっきりしします。
今回のケースでいうと、serde_jsonというクレートがJSON -> Serde data modelをすでに定義してくれているので、私達はSerde data model -> Responseを定義できればOKということになります
Deserialize, Deserializer, Visitor
入力データのJSONをResponse構造体にデシリアライズするために私達がやらなくてはならないことは、Serde data modelをどうやってResponseにマッピングするかを定義することです。そのためにはResponseにDeserialize traitを実装する必要があります。具体的な実装に移る前に、そのとき現れる3つの概念について説明します。
Visitor
Visitorは以下のように定義されるtraitで、Serde data modelを具体的に、どのように構造体にマッピングするかの振る舞いを定義するためのtraitです。
例えば、fn visit_seq()は、Serde data modelにおいて、様々なデータ型を含む連続したデータ構造を表すseq をどのようにtype Valueにマッピングするべきかを定義します
pub trait Visitor<'de>: Sized {
type Value;
// Required method
fn expecting(&self, formatter: &mut Formatter<'_>) -> Result;
// Provided methods
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where E: Error { ... }
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where E: Error { ... }
...
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where A: SeqAccess<'de> { ... }
....
}
Deserializer
Deserializerは入力データをSerde data modelにどのようにマッピングするかを定義するためのtraitです。実際は、Serde data modelにマッピング後、そのデータ型に対応する適切なVisitorのメソッドを呼び出すことで入力データをRustのデータ型に変換しています。
例えば、通常fn deserialize_seqは、入力データがseqに該当するデータ構造だったとき、V::visit_seqを呼び出し、V::Valueへのマッピングを試みます。
ただしこれはDeserializerの実装次第であるため、本当にV::visit_seqが呼び出されるかはわかりません。
pub trait Deserializer<'de>: Sized {
type Error: Error;
// Required methods
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
...
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
...
}
ここで、deserialize_anyは、特別なメソッドとなっており、JSONやYAMLのように外部情報なしにその構造からデータ型が一意に定まるような自己記述型のデータフォーマットはこのdeserialize_anyを実装していればとりあえずなにが来てもデシリアライズできることが保証されます。例: serde_json::Deserializerはdeserialize_anyを実装しており、あらゆる入力JSONをserde_json::Valueにデシリアライズ可能です。
Deserialize
DeserializeはSerde data modelをRustで定義されたstructやenumデータ構造などのデータ構造にどのようにマッピングすべきかの振る舞いを定義するためのtraitです。
具体的には、Visitorをインスタンス化し、Deserializerに渡すことでデシリアライズを行います。
Deserializeの実装は、一見Visitorとの違いが曖昧ですが、Deserializeが注目しているのはどのVisitorとDeserializerのどのメソッドを対応させるかのヒントを与えることです。
DeserializerとVisitor間の責任境界面をつなげる役割をするのがDeserializetraitというイメージです。
pub trait Deserialize<'de>: Sized {
// Required method
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>;
}
ResponseにDeserializeを実装する
Response構造体に対してDeserializetraitを実装すると最終的に以下のような実装となります。
実質、実装の殆どの部分はResponseVisitorを定義し、Visitor traitを実装する部分です。
impl<'de> Deserialize<'de> for Response {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let visitor = ResponseVisitor;
serde::Deserializer::deserialize_seq(deserializer, visitor)
}
}
Visitor traitの実装
では、ResponseVisitorを定義し、Visitor traitを実装してみます。
結果的に以下のようになります
struct ResponseVisitor;
impl<'de> Visitor<'de> for ResponseVisitor {
type Value = Response;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("tuple array")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let time = serde::de::SeqAccess::next_element::<u64>(seq)?.map_or_else(Self::len_error::<u64, A>, Ok)?;
let value_1 = serde::de::SeqAccess::next_element::<&'de str>(seq)?
.map_or_else(Self::len_error::<&str, A>, Ok)
.and_then(Self::str_to_f64::<A>)?;
let value_2 = serde::de::SeqAccess::next_element::<&'de str>(seq)?
.map_or_else(Self::len_error::<&str, A>, Ok)
.and_then(Self::str_to_f64::<A>)?;
serde::de::SeqAccess::next_element::<&'de str>(&mut seq)?.map_or_else(
|| Ok(()),
|_| {
Err(serde::de::Error::invalid_length(
Self::EXPECT_FIELD_LENGTH,
&format!(
"tuple struct Inner with {} elements",
Self::EXPECT_FIELD_LENGTH
)
.as_str(),
))
},
)?;
Ok(Response {
time,
value_1,
value_2,
})
}
}
impl ResponseVisitor {
const EXPECT_FIELD_LENGTH: usize = 3;
fn len_error<'de, T, A: serde::de::SeqAccess<'de>>() -> Result<T, A::Error> {
Err(serde::de::Error::invalid_length(
Self::EXPECT_FIELD_LENGTH,
&format!(
"expected a tuple struct Response with {} elements",
Self::EXPECT_FIELD_LENGTH,
)
.as_str(),
))
}
fn str_to_f64<'de, A: serde::de::SeqAccess<'de>>(s: &'de str) -> Result<f64, A::Error> {
s.parse::<f64>().map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&format!("double value. error: {}", e).as_str(),
)
})
}
}
今回は、seqで表されるデータ構造をResponseにデシリアライズする方法のみを定義しました。
fn visit_seq()が今回の実装のほぼ全てで、seqデータをどのようにResponseにマッピングするかを定義してくれます。
まず引数のseqはSerde data modelのseqに該当するのデータで、serde::de::SeqAccess<'de>を実装しています。
全体の流れとしてはserde::de::SeqAccess::next_elementをseqに対して順に呼び出すことで、所望の要素が所望の順で格納されていることを確認していき、Responseの各フィールドに対応させるというものです。
最後にこれ以上要素が無いことを確認してResponseを返却しています。
Visitor traitに定義されている他のメソッド(fn visit_map(), fn visit_tupleなど)は期待していない型であることを示すエラーを返す処理がデフォルト実装として定義されています。なので、deserializerがdeserializer::deserialize_seq内でResponseVisitor::visit_mapを呼び出すようなおかしな実装になっているとエラーとなります。
以上で、ResponseにDeserializeが実装できました。
lifetime 'de とは
'deはデシリアライズするデータ構造が入力データの一部を参照する際に使用されるlifetimeです。今回のケース、Responseはすべてのフィールドの所有権を持っていますが、以下のような構造であった場合、'de lifetimeによって入力データが出力データの寿命より早くメモリから消去される可能性を排除することができ、ダングリングポインタの心配なくデシリアライズができます
struct Response<'de> {
s1: &'de str
}
Deserialize と DeserializeOwned
Deserializeを実装していて、かつフィールドの値の所有権をすべて保持している構造体はDeserializeOwnedを自動的に実装します。
DeserializeOwnedの実装は以下のようになっており、Deserializeを実装した型Tが'de lifetimeに依存しないとき、TはDeserializeOwnedを実装していることになります。
pub trait DeserializeOwned: for<'de> Deserialize<'de> {}
impl<T> DeserializeOwned for T where T: for<'de> Deserialize<'de> {}
なので、今回のResponseはDeserializeであり、DeserializeOwnedでもあるということになります
まとめ
標準的なderive macroのDeserialize実装で対応できないデシリアライズに遭遇した場合、Deserialize, Deserializer, Visitorの役割を理解していれば容易に自分の望むようなデシリアライズロジックを組むことができます。derive macroによって提供される実装の方がフィールド単位の細やかな制御を行えたりと便利なのであまり自分でDesrializeを実装することは少ないですが、いざというときにこの記事がお役に経てば幸いです。
serdeに関するより詳細な情報は公式ドキュメントからどうぞ
Discussion