serde::Deserializeを自分で書けるようになろう
目的
この記事の目的は、Rust userの大半が使ったことがあるcrate, serde
のDeserialize
traitを自分で実装できるようにすることです。
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
間の責任境界面をつなげる役割をするのがDeserialize
traitというイメージです。
pub trait Deserialize<'de>: Sized {
// Required method
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>;
}
ResponseにDeserializeを実装する
Response
構造体に対してDeserialize
traitを実装すると最終的に以下のような実装となります。
実質、実装の殆どの部分は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