RustでJSONを扱う(Serde)
JSON はあらゆるところで使われています。設定ファイル、Web API、ゲームのセーブデータなどです。Rust で JSON を扱うときは、ほとんどの人が2つのクレートを使います。serde(serialize / deserialize の略)は、Rust の値をデータに変換したり、データから値に戻したりする方法を知っています。serde_json は、そこに JSON という形式を教えてくれます。
serde は通訳のようなものだと考えてください。データの形を、ふつうの Rust の構造体として一度書いておくだけで、serde が JSON への変換と JSON からの変換をしてくれます。JSON のテキストを手で組み立てる必要も、JSON を1文字ずつ分解する必要もありません。
プロジェクトの中で、2つのクレートを追加します。derive フィーチャーを付けると、変換のためのコードを serde が自動で書いてくれます。
cargo add serde --features derivecargo add serde_jsonこれで Cargo.toml は次のようになります。
[dependencies]serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"Cargo.toml や cargo add が初めてなら、先に Cargoとクレートを読んでください。Rust Playground には両方のクレートが最初から入っているので、このページの「Try it Yourself」ボタンはどれも準備なしで動きます。
構造体から JSON へ
Section titled “構造体から JSON へ”構造体に #[derive(Serialize)] を付けると、serde_json::to_string でその構造体を JSON のテキストに変換できます。
use serde::Serialize;
#[derive(Serialize)]struct User { name: String, age: u32, admin: bool,}
fn main() { let user = User { name: String::from("Ana"), age: 28, admin: false };
let compact = serde_json::to_string(&user).unwrap(); println!("{}", compact);
let pretty = serde_json::to_string_pretty(&user).unwrap(); println!("{}", pretty);}実行結果:
{"name":"Ana","age":28,"admin":false}{ "name": "Ana", "age": 28, "admin": false}to_string は詰めて書かれた JSON を返すので、ネットワーク越しに送るのに向いています。to_string_pretty は改行とスペースを入れてくれるので、人が読むファイルに向いています。キーの順番は、構造体のフィールドの順番と同じになります。
JSON から構造体へ
Section titled “JSON から構造体へ”逆向きの変換はデシリアライズ(deserialize)と呼ばれます。derive のリストに Deserialize を加えて、serde_json::from_str を呼びます。どの型に変換したいかは、型注釈で伝えます。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]struct User { name: String, age: u32, admin: bool,}
fn main() -> Result<(), serde_json::Error> { let text = r#"{"name": "Ana", "age": 28, "admin": false}"#;
let user: User = serde_json::from_str(text)?; println!("{:?}", user); println!("{}さんは{}歳です", user.name, user.age); Ok(())}実行結果:
User { name: "Ana", age: 28, admin: false }Anaさんは28歳ですここでのポイントは2つです。
r#"..."#は生文字列リテラル(raw string)です。この中では"を自由に書けるので、JSON を書くのにとても便利です。from_strはResultを返します。テキストが正しい JSON ではないかもしれないからです。ここではmainがResultを返しているので、?を使ってエラーを呼び出し元に渡せます。
JSON がまちがっているとき
Section titled “JSON がまちがっているとき”実際の JSON は、思ったとおりの形をしていないことがよくあります。フィールドが欠けていたり、数値のはずがテキストで届いたりします。serde はすべてをチェックして、プログラムを落とす代わりにわかりやすいエラーを返してくれます。match で処理しましょう。
use serde::Deserialize;
#[derive(Deserialize, Debug)]struct User { name: String, age: u32,}
fn main() { let inputs = [ r#"{"name": "Ana", "age": 28}"#, r#"{"name": "Ben"}"#, r#"{"name": "Cy", "age": "old"}"#, ];
for text in inputs { match serde_json::from_str::<User>(text) { Ok(user) => println!("OK: {:?}", user), Err(e) => println!("エラー: {}", e), } }}実行結果:
OK: User { name: "Ana", age: 28 }エラー: missing field `age` at line 1 column 15エラー: invalid type: string "old", expected u32 at line 1 column 27エラーメッセージは serde_json が英語で出力するものです。1つ目は「1行目15列目で、フィールド age がありません」、2つ目は「1行目27列目で、型がまちがっています。文字列 “old” が来ましたが、u32 が必要です」という意味です。(エラーに列番号が出るので、この例の JSON は英語のままにしています。日本語の文字に変えると、列の位置が変わってしまうからです。)
::<User> の部分は「ターボフィッシュ」(turbofish)と呼ばれます。ほしい型を伝える別の書き方で、let user: User のように型注釈を書く場所がないときに便利です。
キーが欠けているとき:Option と #[serde(default)]
Section titled “キーが欠けているとき:Option と #[serde(default)]”フィールドが本当に省略可能な場合はどうすればいいでしょうか。良い方法が2つあります。
- フィールドを
Optionにする。キーがない(またはnullの)ときはNoneになります。 #[serde(default)]を付ける。キーがないときは、その型のデフォルト値(0、false、空のString、空のVec)になります。
use serde::Deserialize;
#[derive(Deserialize, Debug)]struct Profile { name: String, bio: Option<String>, #[serde(default)] followers: u32, #[serde(default)] tags: Vec<String>,}
fn main() { let full = r#"{"name": "Ana", "bio": "Rustが大好き", "followers": 120, "tags": ["rust"]}"#; let short = r#"{"name": "Ben"}"#;
let a: Profile = serde_json::from_str(full).unwrap(); let b: Profile = serde_json::from_str(short).unwrap();
println!("{:?}", a); println!("{:?}", b);}実行結果:
Profile { name: "Ana", bio: Some("Rustが大好き"), followers: 120, tags: ["rust"] }Profile { name: "Ben", bio: None, followers: 0, tags: [] }「値がない」ことが「ゼロ」や「空」とは違う意味を持つなら Option を、デフォルト値で十分なら default を使いましょう。
フィールド名を変える
Section titled “フィールド名を変える”Rust ではフィールド名に snake_case を使いますが、多くの Web API は camelCase を使いますし、なかには Rust では使えない名前(type など)を使うものもあります。だからといって、Rust らしい名前をあきらめる必要はありません。構造体全体には rename_all を、ひとつのフィールドには rename を使います。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]#[serde(rename_all = "camelCase")]struct UserProfile { user_id: u64, display_name: String, is_verified: bool, #[serde(rename = "type")] account_type: String,}
fn main() { // Web API が返してきそうなレスポンス let response = r#"{ "userId": 42, "displayName": "Ana Silva", "isVerified": true, "type": "premium" }"#;
let profile: UserProfile = serde_json::from_str(response).unwrap(); println!("{}さん(ID {})は{}アカウントです", profile.display_name, profile.user_id, profile.account_type);
// シリアライズでも同じ名前が使われるので、JSON に戻しても元どおりになる println!("{}", serde_json::to_string(&profile).unwrap());}実行結果:
Ana Silvaさん(ID 42)はpremiumアカウントです{"userId":42,"displayName":"Ana Silva","isVerified":true,"type":"premium"}serde はデフォルトで、構造体にない JSON のキーを単に無視します。だから、API が50個のフィールドを送ってきても、自分が必要なフィールドだけを書けば大丈夫です。
データの一覧:構造体の Vec
Section titled “データの一覧:構造体の Vec”JSON の配列は Vec になります。構造体の中にほかの構造体やベクタを入れることもできるので、深い入れ子の JSON も、型を入れ子にするだけで表せます。
use serde::Deserialize;
#[derive(Deserialize, Debug)]struct Task { title: String, done: bool,}
fn main() { let text = r#"[ {"title": "serde を学ぶ", "done": true}, {"title": "設定ファイルを書く", "done": false}, {"title": "API を呼び出す", "done": false} ]"#;
let tasks: Vec<Task> = serde_json::from_str(text).unwrap();
for task in &tasks { let mark = if task.done { "x" } else { " " }; println!("[{}] {}", mark, task.title); } let left = tasks.iter().filter(|t| !t.done).count(); println!("残りのタスク: {}件", left);}実行結果:
[x] serde を学ぶ[ ] 設定ファイルを書く[ ] API を呼び出す残りのタスク: 2件型の決まっていない JSON:Value と json!
Section titled “型の決まっていない JSON:Value と json!”JSON の形が前もってわからないときや、ほんの一部だけが必要なときもあります。そんなときは serde_json::Value を使います。Value は、オブジェクト、配列、文字列、数値、真偽値、null と、どんな JSON でも入れられます。json! マクロを使うと、JSON そっくりの書き方で Value を作れます。
use serde_json::{json, Value};
fn main() { let data: Value = json!({ "city": "Lisbon", "temps": [18, 21, 19], "sunny": true });
println!("都市: {}", data["city"]); println!("最初の気温: {}", data["temps"][0]);
// as_str() を使うと、JSON の文字列ではなくふつうの &str が得られる if let Some(city) = data["city"].as_str() { println!("ただのテキスト: {}", city); }
// 存在しないキーを指定しても、落ちずに Null が返る println!("風: {}", data["wind"]);}実行結果:
都市: "Lisbon"最初の気温: 18ただのテキスト: Lisbon風: nulldata["city"] を表示するとダブルクォートが付くことに注目してください。まだ JSON の文字列だからです。ふつうの Rust の値がほしいときは、.as_str()、.as_i64()、.as_bool() を使います。値が別の型かもしれないので、どれも Option を返します。
Value は柔軟ですが、コンパイラの助けを受けられなくなります。形がわかっているなら、構造体を使うほうがおすすめです。
JSON ファイルの読み書き
Section titled “JSON ファイルの読み書き”実際によくある例を見てみましょう。アプリの設定を config.json に保存して、また読み込みます。このページの内容とファイル入出力を組み合わせたものです。std::fs::write でテキストを保存し、std::fs::read_to_string で読み込みます。
use serde::{Deserialize, Serialize};use std::error::Error;use std::fs;
#[derive(Serialize, Deserialize, Debug)]struct Config { username: String, theme: String, font_size: u32,}
fn main() -> Result<(), Box<dyn Error>> { let config = Config { username: String::from("ana"), theme: String::from("dark"), font_size: 14, };
// 保存する let text = serde_json::to_string_pretty(&config)?; fs::write("config.json", text)?;
// 読み込む let loaded_text = fs::read_to_string("config.json")?; let loaded: Config = serde_json::from_str(&loaded_text)?; println!("読み込んだ設定: {:?}", loaded); Ok(())}実行結果:
読み込んだ設定: Config { username: "ana", theme: "dark", font_size: 14 }なぜ Box<dyn Error> なのでしょうか。この main は2通りの失敗をする可能性があります。ファイルのエラー(std::io::Error)と JSON のエラー(serde_json::Error)です。Box<dyn Error> は「どんな種類のエラーでもいい」という意味なので、どちらに対しても ? が使えます。くわしくは独自のエラー型のページで説明しています。
よくある間違い
Section titled “よくある間違い”Deserialize の derive を忘れる。 構造体に Serialize しか付けていないと、JSON を書き出すことはできても、読み込むことはできません。エラーメッセージは長いですが、大事なのは次の部分です。
error[E0277]: the trait bound `User: serde::Deserialize<'de>` is not satisfied |10 | let user: User = serde_json::from_str(r#"{"name":"Ana","age":28}"#).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `User` typeこれは「User は serde::Deserialize を実装していません。User 型に #[derive(serde::Deserialize)] を追加することを検討してください」という意味です。直し方は #[derive(Serialize, Deserialize)] と書くことです。もし「cannot find derive macro Serialize」(derive マクロ Serialize が見つからない)と表示されたら、derive フィーチャーなしで serde を追加しています。cargo add serde --features derive を実行してください。
自分で管理していないデータに .unwrap() を使う。 小さな例なら問題ありませんが、ユーザーや Web サイトから来る JSON は壊れているかもしれません。おかしなファイルがひとつあるだけで、プログラムはパニックして落ちてしまいます。上で紹介したように、? や match を使いましょう。
data["name"] がダブルクォートなしで表示されると思ってしまう。 Value は JSON として表示されます。ただのテキストがほしいときは .as_str() を呼びましょう。
1. 買い物リストを保存する
Section titled “1. 買い物リストを保存する”name(String)と qty(u32)を持つ構造体 Item を作ってください。Vec に2つの品物を入れ、1回の呼び出しでベクタ全体をJSONに変換して表示します。
ヒント:#[derive(Serialize)] が必要なのは構造体だけです。シリアライズできるものの Vec は、それ自体もシリアライズでき、JSONの配列になります。
解答を見る
use serde::Serialize;
#[derive(Serialize)]struct Item { name: String, qty: u32,}
fn main() { let list = vec![ Item { name: String::from("りんご"), qty: 3 }, Item { name: String::from("パン"), qty: 1 }, ];
let json = serde_json::to_string(&list).unwrap(); println!("{}", json);}実行結果:
[{"name":"りんご","qty":3},{"name":"パン","qty":1}]serde は Vec、String、数値の扱い方を最初から知っているので、Item に Serialize をderiveするだけで済みます。to_string_pretty を使うと、同じリストが複数行に分かれて表示されるので、試してみてください。
2. デフォルト値のある設定
Section titled “2. デフォルト値のある設定”次のJSONを構造体 Settings に読み込んでください:{"siteTitle": "私のブログ", "postsPerPage": 5}。構造体には site_title、posts_per_page、そして欠けているかもしれない theme を持たせます。続けて {"siteTitle": "メモ"} も読み込みます。こちらでは posts_per_page が 0 になるようにしてください。両方を {:?} で表示します。
ヒント:構造体には #[serde(rename_all = "camelCase")]、theme には Option<String>、posts_per_page には #[serde(default)] が必要です。
解答を見る
use serde::Deserialize;
#[derive(Deserialize, Debug)]#[serde(rename_all = "camelCase")]struct Settings { site_title: String, #[serde(default)] posts_per_page: u32, theme: Option<String>,}
fn main() { let full = r#"{"siteTitle": "私のブログ", "postsPerPage": 5}"#; let short = r#"{"siteTitle": "メモ"}"#;
let a: Settings = serde_json::from_str(full).unwrap(); let b: Settings = serde_json::from_str(short).unwrap();
println!("{:?}", a); println!("{:?}", b);}実行結果:
Settings { site_title: "私のブログ", posts_per_page: 5, theme: None }Settings { site_title: "メモ", posts_per_page: 0, theme: None }rename_all によって、siteTitle は site_title に、postsPerPage は posts_per_page に対応づけられます。どちらのJSONにも theme がないので、2回とも None です。2つ目のJSONには postsPerPage がないので 0 になります。#[serde(default)] を消すと、2つ目の unwrap() が “missing field postsPerPage”(フィールド postsPerPage がない)というメッセージでパニックします。ここで .unwrap() を使っても問題ないのは、JSONを自分で書いたからです。本物の入力には ? か match を使いましょう。
serde(--features derive付き)とserde_jsonを追加し、構造体に#[derive(Serialize, Deserialize)]を付ける。serde_json::to_string/to_string_prettyで値を JSON に、serde_json::from_strで JSON を値に変換する。from_strはResultを返す。実際の入力に対しては、.unwrap()ではなく?やmatchで処理しよう。- 欠けているかもしれないキーには
Optionか#[serde(default)]を、JSON の名前に合わせるにはrename/rename_allを使う。 - 形がわからないときは
serde_json::Valueとjson!を使う。形がわかっているなら構造体を使おう。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. JSON のテキストを自分の構造体に変換できるようにする derive はどれですか?
2. JSON のオブジェクトに bio というキーがないことがあります。そのフィールドに適した型はどれですか?
3. 構造体のフィールドは display_name ですが、JSON では displayName が使われています。構造体全体に対するいちばんかんたんな直し方はどれですか?
4. 構造体にない JSON のキーを、serde はデフォルトでどう扱いますか?
スコア: 0 / 4
さらに詳しく
Section titled “さらに詳しく”- serde.rs — Serde 公式ガイド(英語):serde のしくみの概要です。さまざまなデータ形式の例があります。
- Container attributes — serde.rs(英語):
rename_allやdeny_unknown_fieldsなど、構造体や列挙型全体に付けるオプションです。 - Field attributes — serde.rs(英語):
rename、default、skip、skip_serializing_ifなど、フィールド1つずつに付けるオプションです。 serde_json— docs.rs(英語):クレートのトップページです。型付きのJSON、型のないJSON、json!マクロの例があります。serde_json::Value— docs.rs(英語):as_str、as_i64、getなど、型のないJSONを読むためのメソッドがすべて載っています。