serdeのかゆいところに手が届くserde_with

Rustの[serde](https://github.com/serde-rs/serde)は非常に強力なフレームワークですが、重用していると時折serdeの標準機能では対応できないケースに出くわします。そんな時に便利な[serde_with](https://github.com/jonasbb/serde_with)クレートをご紹介します。

## Display/FromStrとの連携

誰かの連絡先を管理するのに、次のような`Contact`列挙型があるとします。`Phone`なら電話番号、`Email`ならメールアドレスが格納されます。

```rust
#[derive(Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub enum Contact {
    Phone(String),
    Email(String),
}
```

`Contact`は人間が読み書きしやすいよう、`Display`と`FromStr`を実装して文字列との相互変換が可能だとします。`Phone`か`Email`かの区別はプレフィックスで行います。つまり、次のような動作になります。

```rust
#[test]
fn contact_test() {
    // 文字列への変換
    assert_eq!(
        Contact::Phone("0123-45-6789".into()).to_string(),
        "phone:0123-45-6789"
    );
    assert_eq!(
        Contact::Email("taro@example.com".into()).to_string(),
        "email:taro@example.com"
    );
    // 文字列からのパース
    assert_eq!(
        "phone:0123-45-6789".parse(),
        Ok(Contact::Phone("0123-45-6789".into()))
    );
    assert_eq!(
        "email:taro@example.com".parse(),
        Ok(Contact::Email("taro@example.com".into()))
    );
}
```

さて、この`Contact`型を使って`Person`構造体を定義します。

```rust
#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    contact: Contact,
}
```

この`Person`には`Serialize`をderiveで実装しているので、serde_jsonでjsonに変換してあげることができます。

```rust
let person = Person {
    name: "Taro".into(),
    contact: Contact::Email("taro@example.com".into()),
};
let s = serde_json::to_string(&person).unwrap();
println!("{}", s);
```

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

```json
{"name":"Taro","contact":{"Email":"taro@example.com"}}
```

`contact`フィールドの値が`{"Email":"taro@example.com"}`となっており、serdeがデフォルトで実装する列挙型のシリアライズ方式になっています。これは、`Contact`が`Serialize`/`Deserialize`をderiveで実装しているためです。これでも悪くはありませんが、せっかく`Contact`は`Display`を実装しているので、こちらに合わせた表現法にしたいところです。`Contact`に`Serialize`と`Deserialize`を自前で実装しても良いのですが、`Contact`は別の場所でも使っており、デフォルトの`Serialize`/`Deserialize`の実装は変えたくないとします。その場合、2つの解決策が考えられます。

### serdeの属性を使う

serdeでは構造体のフィールドに`serialize_with`と`deserialize_with`を指定して、シリアライズ/デシリアライズを担当する関数を指定することができます。

```rust
#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    #[serde(serialize_with = "contact_serde")] // シリアライズに使う関数を指定
    contact: Contact,
}

// 自前のシリアライズを行う関数
fn contact_serde<S: serde::Serializer>(contact: &Contact, s: S) -> Result<S::Ok, S::Error> {
    s.serialize_str(&contact.to_string()) // to_stringはDisplayに基づき文字列へ変換する
}

fn main() {
    let person = Person {
        name: "Taro".into(),
        contact: Contact::Email("taro@example.com".into()),
    };
    let s = serde_json::to_string(&person).unwrap();
    println!("{}", s);
}
```

この実行結果は以下のようになります。

```json
{"name":"Taro","contact":"email:taro@example.com"}
```

というわけで、`Contact`に実装された`Display`の通り`contact`フィールドの値が設定されました。ここでは省略しましたが、`deserialize_with`を指定すればこのJSONを元の`Contact`へデシリアライズすることもできるでしょう。

### serde_with/DisplayFromStr を使う

serdeの標準機能だと、自前でシリアライズ/デシリアライズする関数を記述する必要がありますが、`serde_with`はそこを含めてクレートが提供してくれます。使い方は、対象となる構造体の前に`#[serde_as]`と属性を書き、対象のフィールドの前に`#[serde_as(as = "...")]`のように対応するシリアライズ方法を記述するというものです。シリアライズを`Display`に、デシリアライズを`FromStr`を使って行いたい場合は、その名の通り`DisplayFromStr`を指定します。

```rust
use serde_with::{serde_as, DisplayFromStr};

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    #[serde_as(as = "DisplayFromStr")]
    contact: Contact,
}

fn main() {
    let person = Person {
        name: "Taro".into(),
        contact: Contact::Email("taro@example.com".into()),
    };
    let s = serde_json::to_string(&person).unwrap();
    println!("JSON:\n{}", s);
    let json = r#"{"name":"Hanako","contact":"email:hanako@example.com"}"#;
    let person: Person = serde_json::from_str(json).unwrap();
    println!("Rust構造体:\n{:?}", person);
}
```

シリアライズ・デシリアライズ両方を自動的に定義してくれるので、JSONとの相互変換も可能です。この実行結果は次のようになります。

```
JSON:
{"name":"Taro","contact":"email:taro@example.com"}
Rust構造体:
Person { name: "Hanako", contact: Email("hanako@example.com") }
```

## コレクション型と使う

### Vec

先程の`Person`型ですが、連絡先を複数持つように変更する場合を考えます。当然`contact`は`Vec`にするわけですが、この場合serdeの`serialize_with`だとうまく表現できません[^1]。このようなコレクション型と組み合わせて使う時、serde_withだと直感的に使うことができます。対応するフィールドに与える属性を`#[serde_as(as = "Vec<DisplayFromStr>")]`とするだけです。

```rust
use serde_with::{serde_as, DisplayFromStr};

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    #[serde_as(as = "Vec<DisplayFromStr>")]
    contact: Vec<Contact>,
}

fn main() {
    let person = Person {
        name: "Taro".into(),
        contact: vec![
            Contact::Email("taro@example.com".into()),
            Contact::Phone("0123-45-6789".into()),
        ],
    };
    let s = serde_json::to_string(&person).unwrap();
    println!("JSON:\n{}", s);
    let json = r#"{"name":"Hanako","contact":["email:hanako@example.com"]}"#;
    let person: Person = serde_json::from_str(json).unwrap();
    println!("Rust構造体:\n{:?}", person);
}
```

この実行結果は以下の通りとなります。

```
JSON:
{"name":"Taro","contact":["email:taro@example.com","phone:0123-45-6789"]}
Rust構造体:
Person { name: "Hanako", contact: [Email("hanako@example.com")] }
```

### HashMap

`Contact`型をキーとした`HashMap`をJSONから読み込みたいとします。JSONは文字列しかキーにできないので、serdeのデフォルト実装では`Contact`をキーとした`HashMap`はJSONで表現することができません。serde_withを使えば、`HashMap`のキーに`Contact`を利用することができます。

```rust
use serde_with::{serde_as, DisplayFromStr};
use std::collections::HashMap;

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct ContactList(#[serde_as(as = "HashMap<DisplayFromStr, _>")] HashMap<Contact, String>);

fn main() {
    let json = r#"
{
    "email:taro@example.com": "boss",
    "phone:0123-45-6789": "friend"
}"#;
    let list: ContactList = serde_json::from_str(json).unwrap();
    println!("{:?}", list);
}
```

この実行結果は以下のようになります。

```
ContactList({Email("taro@example.com"): "boss", Phone("0123-45-6789"): "friend"})
```

## 独自のシリアライズ/デシリアライズ方法を実装する

これまで用いてきた`DisplayFromStr`ですが、これは`SerializeAs`トレイトと`DeserializeAs`トレイトを実装した構造体(unit struct)です。これらのトレイトを実装した独自の構造体を定義すれば、`DisplayFromStr`と同様に、`serde_as`と合わせて使うことで独自のシリアライズ/デシリアライズ方法を定義することができます。

例として、`u8`型を漢数字の文字列としてシリアライズ/デシリアライズする`Kansuji`型を作ってみます。

```rust
use serde_with::{serde_as, DeserializeAs, SerializeAs};

struct Kansuji;

impl SerializeAs<u8> for Kansuji {
    fn serialize_as<S>(source: &u8, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let s = match *source {
            1 => "一",
            2 => "ニ",
            3 => "三",
            4 => "四",
            5 => "五",
            6 => "六",
            7 => "七",
            8 => "八",
            9 => "九",
            10 => "十",
            11 => "十一",
            12 => "十二",
            _ => todo!(), // 13以上は無視する手抜き実装
        };
        serializer.serialize_str(s)
    }
}

impl<'de> DeserializeAs<'de, u8> for Kansuji {
    fn deserialize_as<D>(deserializer: D) -> Result<u8, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer).map_err(serde::de::Error::custom)?;
        let i = match s.as_ref() {
            "一" => 1,
            "ニ" => 2,
            "三" => 3,
            "四" => 4,
            "五" => 5,
            "六" => 6,
            "七" => 7,
            "八" => 8,
            "九" => 9,
            "十" => 10,
            "十一" => 11,
            "十二" => 12,
            _ => todo!(), // 13以上は無視する手抜き実装
        };
        Ok(i)
    }
}

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct Date {
    #[serde_as(as = "Kansuji")]
    month: u8,
    #[serde_as(as = "Kansuji")]
    day: u8,
}

fn main() {
    let date = Date {
        month: 1,
        day: 1,
    };
    let s = serde_json::to_string(&date).unwrap();
    println!("JSON:\n{}", s);

    let json = r#"{"month":"八","day":"八"}"#;
    let date: Date = serde_json::from_str(&json).unwrap();
    println!("Rust構造体:\n{:?}", date);
}
```

この実行結果は以下のようになります。

```
JSON:
{"month":"一","day":"一"}
Rust構造体:
Date { month: 8, day: 8 }
```

先述のコレクション型との連携と合わせると、かなり柔軟にserdeを使いこなせるようになります。

## 最後に

serdeをうまく使えるようになれば、JSON等各種フォーマットの取り扱いが格段に楽になります。serdeだけだと上手く表現できない場合も、このserde_withクレートでかなり補えるので、覚えていて損はないでしょう。

[^1]: https://github.com/serde-rs/serde/issues/723