CLOVER🍀

That was when it all began.

Rustのシリアライズ・デシリアライズフレームワークSerdeでJSONデータを読み書きする

これは、なにをしたくて書いたもの?

Rustでデータ構造を変換するフレームワークとしてSerdeというものがあることに気づいてはいたのですが、ちゃんと
扱ったことがなかったので1度見ておくことにしました。

Serde

SerdeのWebサイトはこちら。

Overview · Serde

GitHubリポジトリーはこちら。

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で扱えるデータ型はこちら。

Serde data model · Serde

derive属性の使い方はこちら。

Using derive · Serde

deriveを使う時のカスタマイズはこちら。

Attributes · Serde

Serdeクレートのドキュメントはこちら。

serde - Rust

ひとまず、SerdeにJSONで慣れていってみましょう。

GitHub - serde-rs/json: Strongly typed JSON library for Rust

Serde JSONクレートのドキュメントはこちらです。

serde_json - Rust

環境

今回の環境はこちら。

$ 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フラグはこちらですね。

serde 1.0.219 - Docs.rs

serde_json 1.0.140 - Docs.rs

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,
}

Overview · Serde

derive属性にSerializeDeserializeを追加することで、シリアライズおよびデシリアライズができるようになります。

テストコード。

    #[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);
    }

ChronoクレートのNativeDateをメンバーに含む構造体。

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Book {
    isbn13: String,
    title: String,
    price: i32,
    publish_date: NaiveDate,
}

Chronoクレートの型はそのままではシリアライズ、デシリアライズできませんが、serdeフィーチャーを使うことで
Serdeを使ったシリアライズ、デシリアライズができるようになります。

chrono::serde - Rust

chrono::naive::serde - Rust

テストコード。

    #[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のSerializerDeserializerを書く例も
書かれています。

Custom date format · Serde

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属性を使おうとすると
独自の型についてはSerializerDeserializerの実装が必要になるので、サードパーティー製のクレートを使う場合は
serdeフィーチャーが提供されていないか確認した方がよさそうですね。