[[📒Articles]] > [[📒2021 Articles]] ![[2021-07-04.jpg|cover-picture]] ## はじめに [[Rust]]の最も有名なpackageの1つに[[serde]]がある。個人的には **[[serde]]を使うために[[Rust]]を使っている** と言っても過言ではないくらいだ。 <div class="link-card-v2"> <div class="link-card-v2-site"> <img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" /> <span class="link-card-v2-site-name">GitHub</span> </div> <div class="link-card-v2-title"> GitHub - serde-rs/serde: Serialization framework for Rust </div> <div class="link-card-v2-content"> Serialization framework for Rust. Contribute to serde-rs/serde development by creating an account on GitHub. </div> <img class="link-card-v2-image" src="https://opengraph.githubassets.com/60af0727580691fa3385d9ef1d140ddf463aff55da9dcfd8f9544213c905726b/serde-rs/serde" /> <a href="https://github.com/serde-rs/serde"></a> </div> [[serde]]は[[シリアライズ]]と[[デシリアライズ]]のインタフェースのみを提供しており、実装は別に提供されている。これらのpackageで目的を果たせることは多い。 - [[serde_json]] - [[serde_yaml]] - [[serde_with]] - [[serde_rusqlite]] - [[serde-repr]] しかし、少し込み入ったことをしたくなったときには実装を自作する必要がある。そのため、実装のコード片を管理するリポジトリを作った。 本記事はそのリポジトリ[[🦉serde-snippets]]を作る過程を記したものだ。 <div class="link-card-v2"> <div class="link-card-v2-site"> <img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" /> <span class="link-card-v2-site-name">GitHub</span> </div> <div class="link-card-v2-title"> GitHub - tadashi-aikawa/serde-snippets: Snippets for serde in Rust </div> <div class="link-card-v2-content"> Snippets for serde in Rust. Contribute to tadashi-aikawa/serde-snippets development by creating an account on Gi ... </div> <img class="link-card-v2-image" src="https://opengraph.githubassets.com/69209ea03659b452b88dba210739e7840c2835f789d2ff4f4e0b38e7c862af37/tadashi-aikawa/serde-snippets" /> <a href="https://github.com/tadashi-aikawa/serde-snippets"></a> </div> ## プロジェクトの作成 [[GitHub]]に[[🦉serde-snippets]]という名前でリポジトリを作成する。Cloneしたら`cargo init`して、`Cargo.toml`にインストールしておきたいpackageを記載する。 ```toml:Cargo.toml [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1.0.64" anyhow = "1.0.41" ``` ## 動作確認 `main.rs`にコードを書く。 ```rust:main.rs use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_json::json; #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)] struct Point { lat: i32, lng: i32, } #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)] struct Human { id: i32, name: String, home: Point, } fn main() -> Result<()> { let ichiro = json!({ "id": 1, "name": "Ichiro", "home": { "lat": 35, "lng": 135, } }); let actual: Human = serde_json::from_value(ichiro)?; assert_eq!(actual.id, 1); assert_eq!(actual.name, "Ichiro"); assert_eq!(actual.home.lat, 35); assert_eq!(actual.home.lng, 135); Ok(()) } ``` 実行すると正常終了するはずだ。 ```shell $ cargo run ``` [[assert_eqマクロ]]に失敗するとエラーが出る。期待値を変更してみると確認できる。 ## 全角を半角に正規化するmodule `wide2ascii.rs`を作成し、`normalize2ascii`モジュールを作る。プロパティに`serde(with = ...)`でモジュールを指定すると、そのプロパティに関連する[[ASCII]]文字がある場合は[[シリアライズ]]/[[デシリアライズ]]のときに正規化されるようにしたい。 ```rust #[serde(with = "normalize2ascii")] name: String, ``` ### ロジック [[unicode-jp]]を使う。日本語の正規化に必要な機能は一通り実装されている素晴らしいpackageだ。 ```diff:Cargo.toml [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1.0.64" anyhow = "1.0.41" + unicode-jp = "0.4.0" ``` 今回は[wide2ascii]関数を使う。[[ASCII]]に存在する英数字記号は一通り対応していそうだ。 ```rust assert_eq!("#&Rust-1.6!", kana::wide2ascii("#&Rust-1.6!")); ``` [wide2ascii]: https://docs.rs/unicode-jp/0.4.0/kana/fn.wide2ascii.html ### normalize2asciiモジュールの作成 `wide2ascii.rs`を作成し、`serialize`と`deserialize`を実装する。 ```rust:wide2ascii.rs pub mod normalize2ascii { use kana::wide2ascii; use serde::{self, Deserialize, Deserializer, Serializer}; pub fn serialize<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { serializer.serialize_str(wide2ascii(value).as_str()) } pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Ok(wide2ascii(s.as_str())) } } ``` primitive型の[[serializer]]や[[deserializer]]は既に提供されており、そこに渡す処理を改造するイメージだ。 ### テスト 同じファイルの後半にテストも書いておく。テストは同時に仕様ともなり得るので非常にコスパがいい。packageではなくsnippetに過ぎないため`normalize2ascii`関数にdocは書いていない。 ```rust:wide2ascii.rs #[cfg(test)] mod test_normalize2ascii { use super::*; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_json::json; #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)] struct Point { #[serde(with = "normalize2ascii")] name: String, lat: i32, lng: i32, } #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)] struct Human { id: String, #[serde(with = "normalize2ascii")] name: String, home: Point, } #[test] fn normalize_deserialize_alphabets_numbers_and_brackets_to_half_wide() -> Result<()> { let ichiro = json!({ "id": "13", "name": "ICHIRO({[イチロウ]})", "home": { "name": "AOMORI", "lat": 35, "lng": 135, } }); let actual: Human = serde_json::from_value(ichiro)?; assert_eq!(actual.id, "13", "Not applied"); assert_eq!(actual.name, "ICHIRO({[イチロウ]})", "Applied"); assert_eq!(actual.home.name, "AOMORI", "Applied nest case"); assert_eq!(actual.home.lat, 35); assert_eq!(actual.home.lng, 135); Ok(()) } #[test] fn normalize_serialize_alphabets_numbers_and_brackets_to_half_wide() -> Result<()> { let e = Human { id: "13".to_string(), name: "ICHIRO({[イチロウ]})".to_string(), home: Point { name: "AOMORI".to_string(), lat: 35, lng: 135, }, }; let actual = serde_json::to_string_pretty(&e)?; assert_eq!( actual, r#" { "id": "13", "name": "ICHIRO({[イチロウ]})", "home": { "name": "AOMORI", "lat": 35, "lng": 135 } }"# .trim() ); Ok(()) } } ``` 実行してオールグリーンならOKだ。 ```shell $ cargo test running 2 tests test wide2ascii::test_normalize2ascii::normalize_deserialize_alphabets_numbers_and_brackets_to_half_wide ... ok test wide2ascii::test_normalize2ascii::normalize_serialize_alphabets_numbers_and_brackets_to_half_wide ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` ### ディレクトリ構成 最終的な`src`配下の構成は以下のようになる。 ```ls  src ├──  main.rs └──  wide2ascii.rs ``` ## まとめ 既存のpackageでは対処できない[[serde]]のモジュールをコード片として管理するため、[[🦉serde-snippets]]を作成し、その過程を紹介した。 今後も必要に応じてモジュールを追加していく予定だ。需要があれば、packageとして公開する実績を解除する可能性もある。少しの間は様子を見て、実績を重ねていこうと思う。