はじめに 👋
現代の多くのアプリケーションは、外部のWeb APIと連携してデータを取得したり、操作したりします。これらのAPIの多くは、データ交換形式としてJSON (JavaScript Object Notation) を採用しています。
RustでJSON APIと連携するには、主に以下の2つの機能が必要になります。
- HTTPクライアント: APIに対してリクエストを送信し、レスポンスを受信する機能。
- JSONシリアライズ/デシリアライズ: Rustのデータ構造(構造体など)とJSON形式の文字列を相互に変換する機能。
このステップでは、Rustで非常に人気のあるクレート(ライブラリ)である reqwest
と serde
を使って、JSON APIとの連携とデータのシリアライズ・デシリアライズを行う方法を学びます。
tokio
ランタイムと async/await
を使った非同期処理を前提とします。
必要なクレートの準備 🛠️
まず、Cargo.toml
に必要なクレートを追加します。reqwest
はHTTP通信、serde
はシリアライズ/デシリアライズのフレームワーク、serde_json
はJSON形式の具体的な処理、tokio
は非同期ランタイムです。
Cargo.toml
の [dependencies]
セクションに以下のように記述します。
[dependencies]
reqwest = { version = "0.12", features = ["json"] } # HTTPクライアント、json機能を有効化
serde = { version = "1.0", features = ["derive"] } # シリアライズ/デシリアライズのフレームワーク、deriveマクロを有効化
serde_json = "1.0" # JSON形式の処理
tokio = { version = "1", features = ["full"] } # 非同期ランタイム
reqwest
の json
フィーチャーを有効にすることで、レスポンスボディを直接JSONとしてパースしたり、リクエストボディにJSONデータを簡単に含めたりできるようになります。serde
の derive
フィーチャーは、構造体や列挙型に #[derive(Serialize, Deserialize)]
を追加するだけで、簡単にシリアライズ・デシリアライズを実装できるようにします。
serde によるシリアライズとデシリアライズ 🔄
serde
はRustのデータ構造と様々なデータ形式(JSON, YAML, TOMLなど)を相互に変換するためのフレームワークです。JSONを扱うには serde_json
クレートを併用します。
基本的な使い方は、変換したいRustの構造体や列挙型に serde::Serialize
と serde::Deserialize
トレイトを実装することです。多くの場合、derive
マクロを使うのが最も簡単です。
データ構造の定義
例として、ユーザー情報を表す構造体を定義してみましょう。
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)] // Debugも追加しておくと表示に便利
struct User {
id: u64,
username: String,
email: String,
active: bool,
}
#[derive(Serialize, Deserialize)]
を追加するだけで、この User
構造体はJSONとの間で相互に変換可能になります。
シリアライズ (Rust → JSON)
Rustのデータ構造のインスタンスをJSON文字列に変換するには、serde_json::to_string
や serde_json::to_string_pretty
(整形されたJSON)を使います。
use serde_json;
fn main() {
let user = User {
id: 1,
username: "rustacean".to_string(),
email: "user@example.com".to_string(),
active: true,
};
// JSON文字列にシリアライズ
let json_string = serde_json::to_string(&user).expect("シリアライズに失敗しました");
println!("Serialized JSON: {}", json_string);
// 整形されたJSON文字列にシリアライズ
let pretty_json_string = serde_json::to_string_pretty(&user).expect("シリアライズに失敗しました");
println!("\nPretty Serialized JSON:\n{}", pretty_json_string);
}
// --- User構造体の定義は省略 ---
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u64,
username: String,
email: String,
active: bool,
}
// --- ここまで ---
実行すると、user
インスタンスがJSON文字列に変換されて出力されます。
デシリアライズ (JSON → Rust)
JSON文字列をRustのデータ構造のインスタンスに変換するには、serde_json::from_str
を使います。
use serde_json;
fn main() {
let json_data = r#"
{
"id": 2,
"username": "ferris",
"email": "ferris@rust-lang.org",
"active": true
}
"#;
// JSON文字列からUser構造体にデシリアライズ
let user: User = serde_json::from_str(json_data).expect("デシリアライズに失敗しました");
println!("\nDeserialized User: {:?}", user);
println!("Username: {}", user.username);
}
// --- User構造体の定義は省略 ---
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u64,
username: String,
email: String,
active: bool,
}
// --- ここまで ---
json_data
文字列が User
構造体のインスタンスに変換され、その内容が表示されます。
expect
を使ってエラー処理を簡略化していますが、実際のアプリケーションでは Result
を適切に処理する必要があります。シリアライズやデシリアライズは失敗する可能性があります(例: JSON形式が不正、型が一致しない)。
reqwest によるAPI連携 🌐
reqwest
クレートを使うと、HTTPリクエストを簡単に送信できます。ここでは、公開されているJSON API (例: JSONPlaceholder) を使って、データの取得 (GET) と送信 (POST) を試してみましょう。
GETリクエスト: APIからデータを取得してデシリアライズ
APIからJSONデータを取得し、それをRustの構造体にデシリアライズする例です。
use reqwest;
use serde::Deserialize;
use tokio; // tokioランタイムを使用
#[derive(Deserialize, Debug)]
struct Post {
#[serde(rename = "userId")] // JSONのキー名とRustのフィールド名が異なる場合
user_id: u64,
id: u64,
title: String,
body: String,
}
#[tokio::main] // main関数を非同期関数にする
async fn main() -> Result<(), reqwest::Error> {
let api_url = "https://jsonplaceholder.typicode.com/posts/1";
println!("{} からデータを取得中...", api_url);
// GETリクエストを送信し、レスポンスを取得
let response = reqwest::get(api_url).await?;
// レスポンスボディをJSONとしてデシリアライズ
// reqwestのjson()メソッドは内部でserde_jsonを呼び出す
if response.status().is_success() {
let post: Post = response.json().await?;
println!("取得した投稿データ: {:?}", post);
println!("タイトル: {}", post.title);
} else {
println!("エラーが発生しました: {}", response.status());
}
Ok(())
}
このコードは、指定されたURLにGETリクエストを送り、返ってきたJSONデータを Post
構造体にデシリアライズして表示します。
#[tokio::main]
マクロを使ってmain
関数を非同期関数のエントリーポイントにします。reqwest::get(url).await?
でGETリクエストを非同期に実行し、Result
を処理します (?
演算子)。response.json::
でレスポンスボディを直接().await? Post
型にデシリアライズします。reqwest
のjson
フィーチャーが必要です。#[serde(rename = "userId")]
のようにアトリビュートを使うことで、JSONのキー名(キャメルケース)とRustのフィールド名(スネークケース)の違いを吸収できます。
POSTリクエスト: RustデータをシリアライズしてAPIに送信
RustのデータをJSONにシリアライズし、それをAPIにPOSTリクエストで送信する例です。
use reqwest;
use serde::{Serialize, Deserialize};
use tokio;
#[derive(Serialize, Deserialize, Debug)]
struct NewPost {
#[serde(rename = "userId")]
user_id: u64,
title: String,
body: String,
}
#[derive(Deserialize, Debug)] // レスポンスを受け取るための構造体
struct CreatedPost {
id: u64,
#[serde(rename = "userId")]
user_id: u64,
title: String,
body: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let api_url = "https://jsonplaceholder.typicode.com/posts";
let new_post = NewPost {
user_id: 1,
title: "Rustからの新しい投稿".to_string(),
body: "reqwest と serde を使ってPOSTリクエストを送信しています。".to_string(),
};
println!("{} にデータを送信中...", api_url);
let client = reqwest::Client::new();
let response = client.post(api_url)
.json(&new_post) // 送信するデータをjson()メソッドで指定 (内部でシリアライズされる)
.send()
.await?;
if response.status().is_success() {
// APIによっては作成されたリソースが返ってくる
let created_post: CreatedPost = response.json().await?;
println!("作成された投稿データ: {:?}", created_post);
println!("新しい投稿ID: {}", created_post.id);
} else {
println!("エラーが発生しました: {} {}", response.status(), response.text().await?);
}
Ok(())
}
このコードは、NewPost
構造体のインスタンスを作成し、それをJSONにシリアライズして指定されたURLにPOSTリクエストとして送信します。
reqwest::Client::new()
でHTTPクライアントを作成します。ヘッダーの設定など、より細かい制御が可能です。client.post(url).json(&data).send().await?
でPOSTリクエストを作成し、.json(&data)
でリクエストボディに含めるデータを指定します。このデータは自動的にJSONにシリアライズされます。- APIからのレスポンス(通常、作成されたリソースの情報が含まれる)を
CreatedPost
構造体にデシリアライズして表示しています。
エラーハンドリングについて 🚧
API連携では様々なエラーが発生する可能性があります。
エラーの種類 | 原因の例 | 対処 |
---|---|---|
ネットワークエラー | DNS解決失敗、接続タイムアウト、サーバーが見つからない | reqwest::Error を適切にハンドリングする。リトライ処理を検討する。 |
HTTPステータスコードエラー | 404 Not Found, 401 Unauthorized, 500 Internal Server Error など | response.status() をチェックし、ステータスコードに応じた処理を行う。エラーレスポンスのボディ(JSON形式の場合もある)をパースして詳細情報を取得する。 |
デシリアライズエラー | JSON形式が不正、期待するフィールドが存在しない、型が一致しない | response.json() や serde_json::from_str が返す Result を適切に処理する。エラー内容をログに出力するなどして原因を特定する。 |
シリアライズエラー | (通常は稀だが)データ構造がシリアライズ不可能な場合 | serde_json::to_string などが返す Result を処理する。 |
実際のアプリケーションでは、これらのエラーを網羅的に考慮し、ユーザーに分かりやすいフィードバックを返したり、ログに記録したりする堅牢なエラーハンドリング機構を実装することが重要です。
Rustの Result
型と ?
演算子、そして thiserror
や anyhow
といったエラーハンドリング支援クレートを活用すると、エラー処理をより簡潔かつ効果的に記述できます。
まとめ ✨
このステップでは、RustでJSON APIと連携するための基本的な方法を学びました。
serde
とserde_json
を使って、Rustのデータ構造とJSON文字列を相互に変換(シリアライズ・デシリアライズ)する方法。reqwest
を使って、HTTPのGETリクエストやPOSTリクエストを送信し、APIと通信する方法。async/await
とtokio
を使った非同期処理の基本。- API連携における基本的なエラーハンドリングの考え方。
これらの知識は、Webサービス、CLIツール、デスクトップアプリケーションなど、ネットワーク通信を行う多くのRustアプリケーション開発において不可欠です。ぜひ実際に手を動かして、様々なAPIとの連携を試してみてください!🚀
コメント