icon

nazo6.dev

一覧に戻る
2023/9/10 2023/12/18 9 min read

SerdeのDeserializerを実装する(Part1)

この記事はZennにも投稿しています

#概要

Serdeで任意の形式のファイルなどをデシリアライズする際にはDeserializerを書く必要があります。この記事では基本的なDeserializerの書き方を解説します。 正直自分もあまり理解していない部分が多々あるのですが世に出ている情報が少ないので書くことにしました。

#コードの概観

serdeのDeserializerを実装するというのはつまり、「Deserializerトレイトを実装した構造体を用意する」ということです。ある文字列をデシリアライズするための関数from_strの概略コードは以下のようになります(あくまでイメージです)。

use serde::de;
 
struct MyDeserializer;
 
impl<'de, 'a> de::Deserializer<'de> for &'a mut MyDeserializer<'de> {
	(ここにDeserializeの実装)
	...
}
 
pub fn from_str<T: Deserialize>(input: &str) -> Result<T, Error> {
	let deserializer = MyDeserializer::new();
	T::deserialize(deserializer)?
}

ここで、MyDeserializerがデシリアライザ本体、T: Deserialize#[derive(Deserialize)]などによりserde::de::Deserializeが実装された型のことですね。

#デシリアライズの基本を理解する

この記事では、超基本的なデシリアライザを実装して流れを理解していくことを目指します。

今回実装するのは、「"true""false"の文字列を受けとり、それをbool型にデシリアライズするだけ」のデシリアライザです。

#プロジェクトの作成

次のコマンドで、プロジェクトを作成し必要になるクレートを追加します。

$ cargo new --lib deserializer-example
$ cd deserializer-example
$ cargo add serde thiserror

#Errorの作成

まずはDeserializerのエラー型を作ります。これはDeserializerの関連型として必須で、serde::de::Errorを実装している必要があります。 Deserializerを実装するのはライブラリであることが多いと思うので今回はthiserrorを使います。

error.rs
use serde::de;
use std::fmt::Display;
use thiserror::Error;
 
#[derive(Debug, Error)]
pub enum DeserializeError {
    #[error("Failed to parse: {0}")]
    Parse(String),
    #[error("Unsupported: {0}")]
    Unsupported(String),
    #[error("Error: {0}")]
    Message(String),
}
 
impl de::Error for DeserializeError {
    fn custom<T: Display>(msg: T) -> Self {
        DeserializeError::Message(msg.to_string())
    }
}

#Deserializerの実装

BoolDeserializerという構造体にDeserializerを適切に実装したものが以下になります。

lib.rs
mod error;
 
use serde::{
    de::{self, Visitor},
    forward_to_deserialize_any,
};
 
use error::DeserializeError;
 
pub struct BoolDeserializer<'de> {
    input: &'de str,
}
 
impl<'de, 'a> de::Deserializer<'de> for &'a mut BoolDeserializer<'de> {
    type Error = DeserializeError;
 
    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>,
    {
        Err(DeserializeError::Unsupported(
            "Unsupported type".to_string(),
        ))
    }
 
    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>,
    {
        if self.input == "true" {
            visitor.visit_bool(true)
        } else if self.input == "false" {
            visitor.visit_bool(false)
        } else {
            Err(DeserializeError::Parse("Invalid boolean value".to_string()))
        }
    }
 
    forward_to_deserialize_any! {
        i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf option unit unit_struct newtype_struct seq tuple
        tuple_struct map struct enum identifier ignored_any
    }
}

#実行してみる

なにはともあれ、実行してみましょう。以下のようなコードとテストを追加します。

lib.rs
fn from_str<'de, T: Deserialize<'de>>(input: &'de str) -> Result<T, DeserializeError> {
    let mut deserializer = BoolDeserializer { input };
    T::deserialize(&mut deserializer)
}
 
#[cfg(test)]
mod test {
    #[test]
    fn deserialize_true() {
        let value: bool = super::from_str("true").unwrap();
        assert!(value);
    }
 
    #[test]
    fn deserialize_error() {
        let value: String = super::from_str("true").unwrap();
        assert_eq!(value, "true");
    }
}

cargo testで実行すると以下のような出力が得られるはずです。

running 2 tests
test test::deserialize_error ... FAILED
test test::deserialize_true ... ok

failures:

---- test::deserialize_error stdout ----
thread 'test::deserialize_error' panicked at 'called `Result::unwrap()` on an `Err` value: Unsupported("Unsupported type")', src/lib.rs:61:53

"true"という文字列が正常にtrueにデシリアライズされ、さらに"true"という文字列であっても型がStringであればUnsupportedのエラーが発生していることがわかります。

#解説

Deserializeを実装するには上で書いたエラー型、そしてdeserialize_で始まる、一連の型をデシリアライズするためのメソッドが必要です。deserialize_メソッドは対応する型がserdeに判別されて呼ばれます。全てのメソッドは以下のページで確認できます。

ただし、forward_to_deserialize_anyマクロを使用することで、それらをdeserialize_anyメソッドに飛ばすことが可能です。 今回はboolのみをデシリアイズするDeserializerであるため、bool以外の全ての型をdeserialize_anyに飛ばしています。そして実際にdeserialize_anyで行われる処理はサポートされていないというエラーメッセージを返すことだけです。

そして重要なのがbool型をデシリアライズするdeserialize_boolメソッドです。今回は受け取った文字列が"true"であればtrue"false"であればfalseを返す処理にしたいわけですが、上の例では単に値を返すのではなく、何やら引数として受け取ったvisitor: Visitorvisit_bool関数で処理をしたものを返しています。 これは何かというと、型側(つまりDeserializetrait)での処理の柔軟性を高めるためのserdeの機構だと思います。次にこのVisitorについて解説します。

#Visitorについて

Visitorについて説明するために、まずは今回デシリアライズしたbool型へのDeserializeトレイトの実装(github)を見てみましょう。

struct BoolVisitor;
 
impl<'de> Visitor<'de> for BoolVisitor {
    type Value = bool;
 
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a boolean")
    }
 
    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(v)
    }
}
 
impl<'de> Deserialize<'de> for bool {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_bool(BoolVisitor)
    }
}

このコードを見ればDeserialize::deserialize -> Deserializer::deserialize_bool -> Visitor::visit_boolの順で処理されていることがよりわかりやすいと思います。

また、先程のコードで実際にdeserialize_boolに渡されていたVisitorBoolVisitorだったということがわかります。そして、BoolVisitorにはexpectingvisit_boolの二つのメソッドが実装されています。詳しくはdocs.rsに解説がありますが、expectingはエラーメッセージに使われ、visit_boolはDeserializerで処理されたbool値を受けとり、Self::Value型を返しています。その他のメソッドは実装されていませんが、デフォルト実装でエラーが返るようになっています。

bool型の実装を見ても当然bool型をそのまま返しているだけなので「これって何の意味があるんだろう」と感じるかもしれません。しかし、例えばbool型からNewType構造体にデシリアライズされてほしいような型があるときにVisitorが役に立ちそうです。先程のテストコードに以下を追加してみます。

    #[test]
    fn deserialize_newtype() {
        use serde::de::{Error, Visitor};
        use std::fmt;
 
        #[derive(Debug, PartialEq)]
        struct NewType(bool);
        struct NewTypeVisitor;
 
        impl<'de> Visitor<'de> for NewTypeVisitor {
            type Value = NewType;
 
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a boolean")
            }
 
            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
            where
                E: Error,
            {
                Ok(NewType(v))
            }
        }
 
        impl<'de> Deserialize<'de> for NewType {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                deserializer.deserialize_bool(NewTypeVisitor)
            }
        }
 
        let value: NewType = super::from_str("true").unwrap();
        assert_eq!(value, NewType(true));
    }

ここで重要なのは、Deserializerには一切手を加えていないということです。つまり、Deserializerはserdeによって決められた基本的なインターフェースさえ実装すればDeserializeを実装するあらゆる型をデシリアライズできるのです。

#最後に

この記事では、簡単なDeserializerを実装してserdeでのデシリアライズの流れを追ってみました。参考になれば幸いです。

また、最初にも書きましたが、自分自身も(特にVisitor周りなど)これで理解が正しいのか曖昧な部分があります。間違いなどがありましたらコメントなどで教えていただけると嬉しいです

#参考にさせていただいたサイト

Deserializerの実装方法についての公式ドキュメント

Visitorの理解を助けてくれました

Share this article:
一覧に戻る

関連記事

2021/12/27

2023/10/20

#tech/lang/rust
memo

Rust

気づきとかいろいろ

Read Article

2024/3/23

#tech/lang/rust#hardware/keyboard/keyball
blog

RustでKeyballのファームウェアを書きたい話

KeyballのファームウェアはQMKを使ったC言語のものになっています。ですがやはりRust、使いたいですよね?

Read Article

2023/5/26

#tech/lang/rust#tech/database
memo

RustでSQLからコードを生成するcornucopiaについて

SQLからRustのコードを生成して安全にデータベース操作ができる。恐らくGoのsqlcと同じ感じなんだと思う。

Read Article

2023/11/15

#tech/lang/rust
memo

Rustでジョブキュー的なもの

実行するコマンド(EnqueueかClear)をチャネルで受け取る

Read Article

2025/12/13

2025/12/14

#tech/lang/rust
blog

RustでデスクトップGUI - gpui入門 Part1 (gpuiの仕組み・状態管理の基礎編)

gpui解説記事のPart1。gpuiのレンダリング方法や状態管理について、実際のソースを見ながら詳しく解説します。

Read Article

2024/4/26

2024/5/22

#tech/lang/rust#hardware/keyboard/keyball
blog

RustとEmbassyでKeyballのファームウェアを作った

以前RustでKeyballのファームウェアを書きたい話で、ATMega32U4向けのファームウェアの作成をRustで試みたという話を書きましたが、結論から言うとこれは諦めてProMicro RP2040向けのファームウェアをRustで書くことにしました。

Read Article

2023/8/27

#tech/lang/rust
memo

Rustのserde_jsonでエラーの発生箇所を知る方法

serde_jsonではパースエラー発生時にどのプロパティでエラーが発生したのかわからない

Read Article

2023/6/27

#tech/lang/rust
memo

Rustのtargetフォルダを軽くする

cargo-sweepを使う

Read Article

2022/2/13

#tech/lang/rust
memo

Rustアプリにwasmerを埋め込む

dioxusを使ってwebでもdesktopでも動くアプリを作りたい

Read Article

2025/3/29

#tech/lang/rust
memo

Rustアプリのメモリ使用量を調査する

主にstatic領域のメモリ使用量を調査するのに有益。embassyの独立したタスクなどのサイズが見れる。

Read Article

2023/12/18

2023/12/19

#tech/lang/rust
blog

SerdeのDeserializerを実装する(Part2 JSON編)

この記事はRust Advent Calendar 2023 シリーズ3の19日目の記事です。

Read Article

2024/5/24

#hardware/keyboard#tech/lang/rust
blog

USB HIDキーボードでメディアキーを操作する方法

USB HIDでは0x80がVolume Up、0x81がVolume Downに割り当てられており、さらに0xEDや0xEEでもVolume UpやDownができそうですが、実はこれらは全て動きません(Windowsでは)。

Read Article

2023/9/1

#tech/lang/rust
blog

prisma-client-rust入門

prisma-client-rustはJavascript向けのORMであるprismaをRustから使えるようにしたものです。実はprismaのコア部分はRustで書かれているためこういうものも作りやすかったんじゃないかと思います。

Read Article

2021/12/25

#tech/lang/rust
memo

tauriでWindows上でproductionビルドでのみ画像が表示されない(fetchエラーが発生する)

誰の役にも立たない気がするけどハマったのでメモ

Read Article

2023/11/18

#tech/lang/rust
blog

tokioで作ったサーバーをdockerで起動すると終了が遅くなるときの対処法

axumなどを作ってRustでサーバーを作るとdocker compose stopなどが微妙に遅くてイライラだったのでそれを解決する方法です。

Read Article

2025/4/9

2025/11/5

#tech/lang/rust
blog

「Rustが嫌いです。」の感想

https://zenn.dev/miguel/articles/f052de93fc9980

Read Article

© 2025 nazo6. All rights reserved.