【Rust】serde で JSON を扱う基本を分かりやすく解説

Rust で serde を用いて JSON を扱う方法について、初心者にも分かりやすく解説します。
serde クレートの概要
serde とは
Web API のレスポンスや設定ファイルなど、開発現場では、JSON 形式のデータを扱うことが多くあります。プログラム内の型のデータを JSON のような文字列形式に変換することを「シリアライズ(serialize)」、逆に JSON 文字列を型のデータに変換することを「デシリアライズ(deserialize)」と言います。
Rust では、このシリアライズ・デシリアライズで serde (サード、サーディと読みます)というクレートを使用します。この記事では、serde を使った JSON の基本的な扱い方について紹介します。
この記事で理解してもらいたい内容は以下の通りです。
serdeは、シリアライズ・デシリアライズの共通の仕組みを提供する。- JSON を扱うには
serde_jsonと組み合わせて使う。 #[derive(Serialize)]で型をJSONに変換でき、#[derive(Deserialize)]で JSON を型に変換できる。- ネストした型や、
Vec<T>、Option<T>などを含む型もそのまま扱える。
以降では、上記について例を用いつつ紹介していきます。
serde と serde_json
serde 本体は、特定のデータ形式(フォーマット)に依存しない共通の仕組みです。そのため、serde 自体には、「JSON に変換する」「JSON から変換する」といった具体的な処理はありません。
実際に、JSON を扱うためには、JSON フォーマットに対応した serde_json というクレートを組み合わせて使用します。このクレートは、serde の仕組みを利用して、JSON フォーマットの読み書きを実装しています。
このように serde は形式に依存しない設計になっているおかげで、JSON 以外にも YAML (serde_yaml) や TOML (toml) など、各フォーマットに対応するクレートを使って同じ書き方で使用することができます。この記事では、最もよく使われる JSON を例に紹介します。
serde を使用した JSON 操作の基本
serde の導入方法
serde と serde_json は、他のクレートと同様に cargo add または Cargo.toml に追記することで利用できます。
cargo add で追加する場合
cargo add serde --features derive cargo add serde_json
Cargo.toml に追記する場合
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"例では、メジャーバージョンとして "1" を指定しています。これにより、互換性のある範囲で最新のバージョンが使用されます。もちろん特定のバージョンを指定しても構いません。
導入時には crates.io の serde や serde_json のページを確認してください。なお、実際にインストールされたバージョンは Cargo.lock ファイルで確認できます。
型を JSON に変換する(Serialize)
基本的な方法
構造体 (struct) を JSON 文字列に変換するシリアライズ (Serialize) から見ていきましょう。対象の型に #[derive(Serialize)] を付与し、serde_json::to_string 関数を使用することで、JSON 形式の文字列に変換することができます。
以下の例では、User という型を定義し、JSON 文字列に変換しています。
use serde::Serialize;
// 構造体を定義し、Serialize トレイトを derive することで
// 構造体を JSON にシリアライズできるようにする
#[derive(Serialize, Debug)]
struct User {
name: String,
age: u32,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
name: String::from("Alice"),
age: 30,
};
// 構造体を JSON 文字列に変換(シリアライズ)
let json = serde_json::to_string(&user)?;
println!("{json}");
Ok(())
}【実行結果】
{"name":"Alice","age":30}定義した型に対して #[derive(Serialize)] を付けておくことで、serde_json::to_string にわたすだけで JSON 文字列に変換できていることが分かります。
なお、serde_json::to_string は変換に失敗する可能性があることから、戻り値は Result 型となります。今回の例では、? 演算子を使ってエラー (serde_json::Error) を上位へ伝播しています。もちろん match を用いて処理する方法も適切ですので設計に応じて検討しましょう。
改行やインデントを入れて整形する方法
改行やインデントを入れて整形した、人間が読みやすい形式の JSON 文字列にしたい場合は、serde_json::to_string_pretty を使用します。
use serde::Serialize;
#[derive(Serialize, Debug)]
struct User {
name: String,
age: u32,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
name: String::from("Alice"),
age: 30,
};
// 読みやすいように改行・インデント付きの JSON 文字列に変換する
let json = serde_json::to_string_pretty(&user)?;
println!("{json}");
Ok(())
}【実行結果】
{
"name": "Alice",
"age": 30
}設定ファイルの出力やデバッグ時など、見やすさを重視したい場合には、serde_json::to_string_pretty を使用するのが便利です。
JSON を型に変換する(Deserialize)
次に、JSON 文字列を構造体 (struct) に変換するデシリアライズ (Deserialize) を見ていきましょう。対象の型に #[derive(Deserialize)] を付与し、serde_json::from_str 関数を使用することで、JSON 形式の文字列から構造体に変換することができます。
use serde::Deserialize;
// 構造体を定義し、Deserialize トレイトを derive することで
// JSON 文字列を構造体にデシリアライズできるようにする
#[derive(Deserialize, Debug)]
struct User {
name: String,
age: u32,
}
fn main() -> Result<(), serde_json::Error> {
let json = r#"{"name":"Alice","age":30}"#;
// JSON 文字列を構造体に変換(デシリアライズ)
let user: User = serde_json::from_str(json)?;
println!("{user:?}");
Ok(())
}【実行結果】
User { name: "Alice", age: 30 }定義した型 (User)に対して #[derive(Deserialize)] を付けておき、serde_json::from_str に JSON 文字列を渡せば User 構造体に変換できます。なお、「let user: User」 のように変換先の型注釈を明示している点に注意してください。serde_json::from_str は様々な型に変換できる関数のため、どの型に変換するのかを伝える必要があります。
また、serde_json::from_str も変換に失敗する可能性があることから、戻り値は Result 型となります。例えば、フィールドが一致しない場合や、型が合わない場合にエラーとなります。
Serialize と Deserialize を同時に使用する
実際の開発では、ある型に対してシリアライズとデシリアライズを両方行えるようにするケースがほとんどです。その場合には、#[derive(Serialize, Deserialize)] のように両方をまとめて指定します。
use serde::{Deserialize, Serialize};
// Serialize と Deserialize の両方を derive することで
// シリアライズとデシリアライズの両方に対応できる
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
age: u32,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
name: String::from("Alice"),
age: 30,
};
// 構造体を JSON 文字列に変換(シリアライズ)
let json = serde_json::to_string(&user)?;
println!("{json}");
// JSON 文字列を構造体に変換(デシリアライズ)
let deserialized_user: User = serde_json::from_str(&json)?;
println!("{deserialized_user:?}");
Ok(())
}【実行結果】
{"name":"Alice","age":30}
User { name: "Alice", age: 30 }以降では、いくつかよく使用するパターンを紹介します。その際には、#[derive(Serialize, Deserialize)] のように両方を付けた形で紹介していきます。
よく使用するパターン
ネストした型を含む場合
型を定義する際に、構造体のフィールドに別の構造体を持たせることはよくあります。このような場合に、シリアライズ・デシリアライズをする場合には両方に #[derive(Serialize, Deserialize)] を付けて定義しておく必要があることに注意してください。
以下の例では、User 構造体のフィールドとして Address 構造体を持っています。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Address {
city: String,
zip: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
address: Address,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
name: String::from("Alice"),
address: Address {
city: String::from("Tokyo"),
zip: String::from("100-0001"),
},
};
// 構造体を JSON 文字列に変換(シリアライズ)
let json = serde_json::to_string_pretty(&user)?;
println!("{json}");
// JSON 文字列を構造体に変換(デシリアライズ)
let deserialized_user: User = serde_json::from_str(&json)?;
println!("{deserialized_user:?}");
Ok(())
}
【実行結果】
{
"name": "Alice",
"address": {
"city": "Tokyo",
"zip": "100-0001"
}
}
User { name: "Alice", address: Address { city: "Tokyo", zip: "100-0001" } }例のようにネストした構造体を含めて JSON 文字列に変換できていることが分かります。また、デシリアライズでも、ネストした JSON をそのまま User 構造体に変換できています。
Vec<T> や配列のフィールドを含む場合
複数の値を持つコレクションである Vec<T> についても特別な対応をすることなく、そのまま扱うことが可能です。Vec<T> は JSON の配列に対応します。
以下の例では、先ほどの例における Address を Vec<Address> として表現しています。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Address {
city: String,
zip: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
addresses: Vec<Address>,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
name: String::from("Alice"),
addresses: vec![
Address {
city: String::from("Tokyo"),
zip: String::from("100-0001"),
},
Address {
city: String::from("Osaka"),
zip: String::from("530-0001"),
},
],
};
// 構造体を JSON 文字列に変換(シリアライズ)
let json = serde_json::to_string_pretty(&user)?;
println!("{json}");
// JSON 文字列を構造体に変換(デシリアライズ)
let deserialized_user: User = serde_json::from_str(&json)?;
println!("{deserialized_user:?}");
Ok(())
}【実行結果】
{
"name": "Alice",
"addresses": [
{
"city": "Tokyo",
"zip": "100-0001"
},
{
"city": "Osaka",
"zip": "530-0001"
}
]
}
User { name: "Alice", addresses: [Address { city: "Tokyo", zip: "100-0001" }, Address { city: "Osaka", zip: "530-0001" }] }Vec<Address> が JSON の配列として表現されていることが分かります。例は、作成した構造体に対してですが、Vec<i32> や Vec<String> のように基本的な型の場合も同様です。
なお、[T; N] のような固定長配列も同様に扱えますが、デシリアライズ時に要素数が一致しないとエラーになります。実用上は要素数を柔軟に扱える Vec<T> を使うことがほとんどです。
Option<T> のフィールドを含む場合
値が存在する場合と存在しない場合がある場合には、Option<T> を使用します。serde では、Option<T> も自然に扱うことができます。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
email: Option<String>,
}
fn main() -> Result<(), serde_json::Error> {
let user1 = User {
name: String::from("Alice"),
email: Some(String::from("alice@example.com")),
};
let user2 = User {
name: String::from("Bob"),
email: None,
};
// 構造体を JSON 文字列に変換(シリアライズ)
let json1 = serde_json::to_string(&user1)?;
let json2 = serde_json::to_string(&user2)?;
println!("{json1}");
println!("{json2}");
// JSON 文字列を構造体に変換(デシリアライズ)
let deserialized_user1: User = serde_json::from_str(&json1)?;
let deserialized_user2: User = serde_json::from_str(&json2)?;
println!("{deserialized_user1:?}");
println!("{deserialized_user2:?}");
Ok(())
}【実行結果】
{"name":"Alice","email":"alice@example.com"}
{"name":"Bob","email":null}
User { name: "Alice", email: Some("alice@example.com") }
User { name: "Bob", email: None }Option<T> を使用する場合、None は null として JSON 文字列に出力されます。デシリアライズの場合は、JSON 文字列に当該キーが存在しないか、null であれば None に、値があれば Some(値) という形に変換されます。
null を出力せずキー自体を出力したくない場合
Option 型の値が None の際に、null を出力するのではなく、キー自体を出力したくないような場合には、#[serde(skip_serializing_if = "Option::is_none")] を対象のフィールドに付与します。
...(省略)...
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
// None の場合はキーごと出力しない
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
}
...(省略)...【実行結果】
{"name":"Alice","email":"alice@example.com"}
{"name":"Bob"}
User { name: "Alice", email: Some("alice@example.com") }
User { name: "Bob", email: None }例の結果のようにキー自体が出力されなくなったことが分かります。
まとめ
Rust で serde を用いて JSON を扱う方法について解説しました。serde の入門として最低限理解してほしい内容を中心に説明し、よくある使用例なども紹介しました。
serde は、外部 API との通信や設定ファイルの読み書きなど、様々な場面で利用される非常に重要なクレートで、Rust の学習時に代表例としてよく出てくるものです。今回紹介した基本をしっかりと押さえておくことで今後の様々なクレートの活用にもつながります。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

