到達点は、外部データを型へ変換した後も、値の妥当性を調べることです。前提は第07・11回です。
文法、型、業務ルールの三段階
JSONの文法が正しくても、必要な項目があるとは限りません。型が合っていても、学習時間が一日を超えるなど業務上の不正があり得ます。Serdeはシリアライズとデシリアライズを支援しますが、アプリ独自のルールまですべて自動で知っているわけではありません。
新しいCargoプロジェクトのCargo.tomlを次にします。ここでは教材用の固定版です。
[package]
name = "json-practice"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "=1.0.228", features = ["derive"] }
serde_json = "=1.0.145"
src/main.rsは次のとおりです。
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct Session {
language: String,
minutes: u32,
}
fn parse_session(text: &str) -> Result<Session, Box<dyn std::error::Error>> {
let session: Session = serde_json::from_str(text)?;
if session.language.trim().is_empty() || session.minutes > 1440 {
return Err("学習データの値が不正です".into());
}
Ok(session)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = r#"{"language":"Rust","minutes":25}"#;
let session = parse_session(input)?;
println!("{} {}", session.language, session.minutes);
println!("{}", serde_json::to_string(&session)?);
Ok(())
}
cargo runでRustと25、同じ意味のJSONが出ます。実ファイルなら std::fs::read_to_string(path)? などで取得して同じ変換へ渡します。大きな入力にはサイズ制限と、必要ならBufReaderを使った逐次処理を検討します。
ファイルを閉じることと保存の成功
RustのFileは所有値が破棄されると閉じられます。ただしバッファ付き書き込みの終了処理だけに頼ると、最後の書き込みエラーを明示的に扱いにくくなります。flushやsync_allなど、必要な保証に応じた操作の結果を確認します。
deny_unknown_fieldsは未知の項目を拒否します。互換性の方針によっては未知項目を許す設計もありますが、読み込んで再保存するときに情報を失わないか考えます。
練習と解答
練習:minutesを負の数、文字列、2000に変えて、それぞれどの段階で失敗するか確認します。
解答:負の数と文字列はu32へのデシリアライズで失敗し、2000は型として受け入れた後の業務検証で失敗します。
公式資料
Serde derive、serde_json、std::fsを参照できます。
Rust全20回の目次 | 前の回 | 次の回