1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで学術論文からテキスト抽出するクレートを実装するAdvent Calendar 2024

Day 24

Rustで学術論文からテキストを抽出する #24 応用編 - arXiv論文収集システム構築 Appendix (1) serde

Last updated at Posted at 2024-12-25

Summary

  • Rustのserdeクレートは推せる

今回のシステム実装で一番お世話になっているクレートがおそらくserdeです.
あれこれ実装する中で,いくつかのserdeの機能にも使い慣れてきたので,Appendixその1ではserdeの概要や使い方を改めて紹介しようと思います.

serdeクレート

改めて,serdeは以下のようなクレートです.

Serde Overview
Serde is a framework for serializing and deserializing Rust data structures
efficiently and generically.

The Serde ecosystem consists of data structures that know how to serialize and
deserialize themselves along with data formats that know how to serialize and
deserialize other things. Serde provides the layer by which these two groups
interact with each other, allowing any supported data structure to be serialized
and deserialized using any supported data format.

---
Serdeは、Rustのデータ構造を効率的かつ汎用的にシリアライズおよびデシリアライズするための
フレームワークです。

Serdeエコシステムは、自身をシリアライズおよびデシリアライズする方法を知っているデータ構造と、
他のものをシリアライズおよびデシリアライズする方法を知っているデータフォーマットで構成されて
います。Serdeはこれらの2つのグループが相互にやり取りするためのレイヤーを提供し、サポート
されている任意のデータ構造を、サポートされている任意のデータフォーマットを使用してシリアライズ
およびデシリアライズできるようにします。

今回はserde_jsonしか使っていませんが,serde自体はJSONに限らず様々なフォーマットのSerialize/Deserializeに適用できます.
以下はOverviewで紹介されているリストの一部.

フォーマット crate
JSON https://github.com/serde-rs/json
YAML https://github.com/dtolnay/serde-yaml
TOML https://docs.rs/toml
URL https://docs.rs/serde_qs
CSV https://docs.rs/csv
Pickle https://github.com/birkenfeld/serde-pickle

Pickleまであるのは驚きでした.
RustとPythonは使い所が違い,データ連携できると助かるケースもあるので,いずれ試してみたいです.

基本的な使い方 (serde_json)

ここからはJSON形式に絞って,まずは基本的な使い方を確認します.

構造体 ↔︎ JSON

serde_jsonのGitHubリポジトリに載っているコードほとんどそのままですが,構造体を定義してJSONテキストをDeserialzie→Serializeする処理は以下のようになります.

use serde::{Deserialize, Serialize};

// 構造体定義
#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

// JSONテキスト
let data = r#"
    {
        "name": "John Doe",
        "age": 43,
        "phones": [
            "+44 1234567",
            "+44 2345678"
        ]
    }"#;

// JSONテキスト→Rust構造体にDeserialize
let p: Person = serde_json::from_str(data)?;

// Rust構造体→JSONテキストにSerialize
let data = serde_json::to_string_pretty(&p);
println!("{}", data);
// {
//    "name": "John Doe",
//    "age": 43,
//    "phones": [
//        "+44 1234567",
//        "+44 2345678"
//    ]
// }

Enum ↔︎ JSON

構造体だけでなく,Enumも使えます.
以下はserdeのドキュメントからのサンプル.

#[derive(serde::Serialize)]
enum E {
    W { a: i32, b: i32 },
    X(i32, i32),
    Y(i32),
    Z,
}

let w = E::W { a: 0, b: 0 };
println!("{}", serde_json::to_string(&w).unwrap());
// {"W":{"a":0,"b":0}}

let x = E::X(0, 0);
println!("{}", serde_json::to_string(&x).unwrap());
// {"X":[0,0]}

let y = E::Y(0);
println!("{}", serde_json::to_string(&y).unwrap());
// {"Y":0}

let z = E::Z;
println!("{}", serde_json::to_string(&z).unwrap());
// "Z"

Tips

Deserialize時にデフォルト値を与えたい

APIを操作していると,必ずしも完全な形のJSONが返ってこないことがよくあります.そんなときに,構造体としてフィールドだけは保持しつつ,Deserializeできなかった場合にはデフォルトで設定した値を格納できる機能があります.

以下のように実装します.

#[derive(Deserialize, Default)]
struct Page {
    #[serde(default = "String::new")]
    pub id: String,
    #[serde(default = "u32::default"]
    pub page_num: u32
}

#[serde(default = "")]にデフォルト値を返す関数を記載しておくことで,Deserializeできなかった場合に指定された関数を実行します.
この機能を知ってから,構造体に#[derive(Default)]を指定しておくことのありがたみに気がつきました.

#[derive(Default)]は,Defaultトレイトを実装してくれるもので,手動で実装する場合は以下のようになります.

impl Default for Page {
    fn default() -> Self {
        Page {
            ...
        }
    }
}

今回のシステム実装では,特殊なデフォルト値を返すケース以外は,#[derive(Default)]を実装しておけば大丈夫でした.

JSONのnullを扱いたい

JSONではよく返ってくるnullですが,Rustはそのままではnullを扱えません.
RustではnullNoneOption<>を使って対処しますが,serde_jsonではOption<>を指定することでnullのDeserializeに対応してくれます.

#[derive(Deserialize, Default)]
struct Page {
    pub id: String,
    pub page_num: u32,
    pub description: Option<String>,
}

上記のようにOption<>を使ったフィールドを定義しておくと,description: nullだった場合にはNoneが設定されるようになっています.

難点としては,構造体のフィールドにアクセスするときに毎回unwrap()をかまさなくてはいけない点と,Option<>unwrap()するため,所有権の移動が発生してしまい少々扱いが複雑になってしまう点でしょうか.

APIの実装で構造体のネストが深くなるとunwrap()clone()だけでコードが冗長に見えてしまうため,ここだけは改善したい点でした.

Serialize時に特定のフィールドだけ除外したい

これは,APIでJSONを受け取るときにはフィールドとして持っておきたいが,JSONをPOSTするときには設定したくないフィールドがある場合に便利でした.
created_timeのようなフィールドが該当します.
また,Option<>付きのフィールドで,Noneの場合にはSerializeしたくないというケースも結構ありました.

以下のように対応します.

#[derive(Deserialize, Default)]
struct Page {
    pub id: String,
    pub page_num: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing)]
    pub created_time: String
}

skip_serializing_ifは特定の条件を満たした場合に当該フィールドをSerialize対象から除外する設定で,Option<>付きフィールドには基本的に設定するようにしていました.

skip_serializingは無条件にSerialize対象からフィールドを除外する設定です.

その他

serde.rsのExamplesページには,上記以外にも様々な場合に対応できるSerialize/DeserializeのTipsが載っています.

まとめ

serdeの概要と今回のシステム実装で使用した機能を紹介しました.
Rustはサードパーティのクレートを前提としたコアライブラリの薄い言語ですが,serdeの立ち位置は絶妙だと思います.
Rustの言語と同じ発想なのか,Serialize/Deserializeに関するコアな機能は提供しつつ,各フォーマットに対応する個別の処理は他のサードパーティ製クレートに処理を委譲しています.

Rustのサードパーティライブラリはまだまだ発展途上ですが,serde周辺のエコシステムは非常にバランスがよく使いやすいと思っています.いずれRustのエコシステムもこのように成長していってほしいです.

serdeはリポジトリも積極的にメンテナンスされているので,安心して利用することができる点もポイント高いです.

まとめると,serdeは推せる,と思いました.

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?