これは、なにをしたくて書いたもの?
Rustでデータ構造を変換するフレームワークとしてSerdeというものがあることに気づいてはいたのですが、ちゃんと
扱ったことがなかったので1度見ておくことにしました。
Serde
SerdeのWebサイトはこちら。
GitHub - serde-rs/serde: Serialization framework for Rust
Serdeは、Rustrのデータ構造を効率的かつ汎用的にシリアライズ・デシリアライズするためのフレームワークです。
Serde is a framework for serializing and deserializing Rust data structures efficiently and generically.
特徴としては、多くの言語がデータのシリアライズにリフレクションを使用することに対して、SerdeはRustの強力な
トレイトシステムをベースに構築されたものだとしています。
Where many other languages rely on runtime reflection for serializing data, Serde is instead built on Rust's powerful trait system.
データ構造とは、Serdeのトレイトを実装しているかderive属性を使用した実装を自動生成したものを指しています。
これによりリフレクションや実行時に型情報を扱うオーバーヘッドを回避できます。
A data structure that knows how to serialize and deserialize itself is one that implements Serde's Serialize and Deserialize traits (or uses Serde's derive attribute to automatically generate implementations at compile time). This avoids any overhead of reflection or runtime type information.
つまり、Serdeはデータ構造によらず手書きのシリアライザーと同等の速度で動作する、ということです。
データフォーマットの実装は、コミュニティにより以下が実装されているようです。
- JSON
- Postcard
- CBOR
- YAML
- MessagePack
- TOML
- Pickle
- RON
- BSON
- Avro
- JSON5
- x-www-form-urlencoded QueryString
- Starlark
- Envy
- Envy Store
- S式
- D-Bus
- FlexBuffers
- Bencode
- Token streams
- DynamoDB Items
- Hjson
- CSV
Serdeで扱えるデータ型はこちら。
derive属性の使い方はこちら。
deriveを使う時のカスタマイズはこちら。
Serdeクレートのドキュメントはこちら。
ひとまず、SerdeにJSONで慣れていってみましょう。
GitHub - serde-rs/json: Strongly typed JSON library for Rust
Serde JSONクレートのドキュメントはこちらです。
環境
今回の環境はこちら。
$ rustup --version rustup 1.28.1 (f9edccde0 2025-03-05) info: This is the version for the rustup toolchain manager, not the rustc compiler. info: The currently active `rustc` version is `rustc 1.86.0 (05f9846f8 2025-03-31)`
準備
Cargoパッケージの作成。
$ cargo new --vcs none serde-json-getting-started $ cd serde-json-getting-started
Serde、Serde JSONクレートを追加。derive属性を使うには、Serdeにderiveフィーチャーが必要になります。
また日時を扱うためにChronoクレートも入れておきます。
$ cargo add serde --features derive $ cargo add serde_json $ cargo add chrono --features serde
featureフラグはこちらですね。
Cargo.toml
[package]
name = "serde-json-getting-started"
version = "0.1.0"
edition = "2024"
[dependencies]
chrono = { version = "0.4.40", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
Serde JSONをテストコードで試してみる
それでは、Serde JSONを使ってみましょう。
ソースコードの雛形はこんな感じにします。
src/main.rs
use chrono::NaiveDate; use serde::{Deserialize, Serialize}; fn main() {} // ここに構造体を書く #[cfg(test)] mod tests { use chrono::NaiveDate; use serde_json::{Value, json}; use crate::{Book, Point}; // ここにテストを書く }
まずはSerdeのトップページに書かれてある構造体を使ってみます。
#[derive(Serialize, Deserialize, Debug, PartialEq)] struct Point { x: i32, y: i32, }
derive属性にSerialize、Deserializeを追加することで、シリアライズおよびデシリアライズができるようになります。
テストコード。
#[test] fn getting_started_serde_json() { let point = Point { x: 1, y: 2 }; let serialized = serde_json::to_string(&point).unwrap(); assert_eq!(serialized, r#"{"x":1,"y":2}"#); let deserialized: Point = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, Point { x: 1, y: 2 }); }
それぞれserde_json::to_stringでシリアライズ、serde_json::from_strでデシリアライズを行ったテストコードです。
Crate serde_json / Parsing JSON as strongly typed data structures
またjson!マクロを使うことでJSONデータを組み立てることもできます。この場合、型定義されていない汎用のJSON型に
なります。
#[test] fn using_json_macro() { let point: Value = json!({ "x": 1, "y": 2 }); assert_eq!(point.get("x").unwrap(), 1); assert_eq!(point.get("y").unwrap(), 2); }
- Crate serde_json / Constructing JSON values
- Crate serde_json / Operating on untyped JSON values
- Value in serde_json - Rust
ChronoクレートのNativeDateをメンバーに含む構造体。
#[derive(Serialize, Deserialize, Debug, PartialEq)] struct Book { isbn13: String, title: String, price: i32, publish_date: NaiveDate, }
Chronoクレートの型はそのままではシリアライズ、デシリアライズできませんが、serdeフィーチャーを使うことで
Serdeを使ったシリアライズ、デシリアライズができるようになります。
テストコード。
#[test] fn book_json() { let book = Book { isbn13: "978-4873119786".to_string(), title: "プログラミングRust 第2版".to_string(), price: 5280, publish_date: NaiveDate::from_ymd_opt(2022, 1, 19).unwrap(), }; let serialized = serde_json::to_string(&book).unwrap(); assert_eq!( serialized, r#"{"isbn13":"978-4873119786","title":"プログラミングRust 第2版","price":5280,"publish_date":"2022-01-19"}"# ); let deserialized: Book = serde_json::from_str(&serialized).unwrap(); assert_eq!( deserialized, Book { isbn13: "978-4873119786".to_string(), title: "プログラミングRust 第2版".to_string(), price: 5280, publish_date: NaiveDate::from_ymd_opt(2022, 1, 19).unwrap(), } ); }
今回はChronoクレートのフィーチャーを使いましたが、Serdeでは自分でChronoのSerializer、Deserializerを書く例も
書かれています。
Vectorを使ったテストコード。
#[test] fn book_vec_json() { let books = vec![ Book { isbn13: "978-4873119786".to_string(), title: "プログラミングRust 第2版".to_string(), price: 5280, publish_date: NaiveDate::from_ymd_opt(2022, 1, 19).unwrap(), }, Book { isbn13: "978-4065369579".to_string(), title: "RustによるWebアプリケーション開発 設計からリリース・運用まで".to_string(), price: 4400, publish_date: NaiveDate::from_ymd_opt(2024, 9, 30).unwrap(), }, ]; let serialized = serde_json::to_string(&books).unwrap(); assert_eq!( serialized, r#"[{"isbn13":"978-4873119786","title":"プログラミングRust 第2版","price":5280,"publish_date":"2022-01-19"},{"isbn13":"978-4065369579","title":"RustによるWebアプリケーション開発 設計からリリース・運用まで","price":4400,"publish_date":"2024-09-30"}]"# ); let desealized: Vec<Book> = serde_json::from_str(&serialized).unwrap(); assert_eq!( desealized, vec![ Book { isbn13: "978-4873119786".to_string(), title: "プログラミングRust 第2版".to_string(), price: 5280, publish_date: NaiveDate::from_ymd_opt(2022, 1, 19).unwrap(), }, Book { isbn13: "978-4065369579".to_string(), title: "RustによるWebアプリケーション開発 設計からリリース・運用まで" .to_string(), price: 4400, publish_date: NaiveDate::from_ymd_opt(2024, 9, 30).unwrap(), }, ] ); }
こんなところでしょうか。
おわりに
Rustのシリアライズ・デシリアライズフレームワークであるSerdeを使って、JSONデータを読み書きしてみました。
Serdeはよく見かけるので、どういうものか把握しておけてよかったと思うのと、derive属性を使おうとすると
独自の型についてはSerializer、Deserializerの実装が必要になるので、サードパーティー製のクレートを使う場合は
serdeフィーチャーが提供されていないか確認した方がよさそうですね。