コンテンツにスキップ

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 が自動で書いてくれます。

Terminal window
cargo add serde --features derive
cargo 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」ボタンはどれも準備なしで動きます。

構造体に #[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 は改行とスペースを入れてくれるので、人が読むファイルに向いています。キーの順番は、構造体のフィールドの順番と同じになります。

逆向きの変換はデシリアライズ(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 は、思ったとおりの形をしていないことがよくあります。フィールドが欠けていたり、数値のはずがテキストで届いたりします。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 を使いましょう。

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個のフィールドを送ってきても、自分が必要なフィールドだけを書けば大丈夫です。

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
風: null
▶ 実際に試してみよう

data["city"] を表示するとダブルクォートが付くことに注目してください。まだ JSON の文字列だからです。ふつうの Rust の値がほしいときは、.as_str()、.as_i64()、.as_bool() を使います。値が別の型かもしれないので、どれも Option を返します。

Value は柔軟ですが、コンパイラの助けを受けられなくなります。形がわかっているなら、構造体を使うほうがおすすめです。

実際によくある例を見てみましょう。アプリの設定を 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> は「どんな種類のエラーでもいい」という意味なので、どちらに対しても ? が使えます。くわしくは独自のエラー型のページで説明しています。

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() を呼びましょう。

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 を使うと、同じリストが複数行に分かれて表示されるので、試してみてください。

次の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! を使う。形がわかっているなら構造体を使おう。

理解度チェック

1. JSON のテキストを自分の構造体に変換できるようにする derive はどれですか?

2. JSON のオブジェクトに bio というキーがないことがあります。そのフィールドに適した型はどれですか?

3. 構造体のフィールドは display_name ですが、JSON では displayName が使われています。構造体全体に対するいちばんかんたんな直し方はどれですか?

4. 構造体にない JSON のキーを、serde はデフォルトでどう扱いますか?

スコア: 0 / 4

  • 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を読むためのメソッドがすべて載っています。