環境
- Ubuntu 22.04 LTS
- rustup 1.25.1
- cargo 1.62.1
課題
Rust で WebAPI にアクセスしたい。
reqwest というクレートを使うと HTTP 通信をして返ってきた JSON データを構造体に落とし込むまでをやってくれるのだが、
返ってきた JSON のプロパティ名と自分で定義した構造体のフィールド名は一致していないといけない。つまり、
{
"userName": "田中一郎",
"userAge": 30,
"isPremiumMember": false
}
のような会員情報の JSON を返す WebAPI にアクセスするとき、
struct Member {
userName: String,
userAge: i32,
isPremiumMember: bool
}
のような構造体を定義しておく必要があるのだが、これを実行すると
warning: structure field `userName` should have a snake case name
warning: structure field `userAge` should have a snake case name
warning: structure field `isPremiumMember` should have a snake case name
のように怒られる。Rust の構造体のフィールド名はスネークケースで書くべきだからだ。
このままでも一応コードは動くけれど、大量の warning が出たままなのは気持ちが悪い。
どうすればよいだろうか?
ソースコード
Cargo.toml
はこんな感じ。
reqwest クレートで JSON を構造体に変換するために features に json を指定している。
serde クレートは構造体の直列化に、tokio クレートは非同期処理のために使用している。
[package]
name = "reqwest-sample"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.11.11", features = ["json"] }
serde = { version = "1.0.140", features = ["derive"] }
tokio = { version = "1.20.0", features = ["full"] }
src/main.rs
はこんな感じ。
まず JSON を受け取るための構造体を定義し、impl Member 以下で reqwest を使って WebAPI にアクセスする関数を定義している。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Member {
userName: String,
userAge: i32,
isPremiumMember: bool
}
impl Member {
async fn get(url: &str) -> Result<Self, reqwest::Error> {
let response = reqwest::get(url).await.unwrap();
let json = response.json::<Self>().await.unwrap();
Ok(json)
}
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let url = "https://gist.githubusercontent.com/YUUKIToriyama/1045d7941e4e9dc1c416b120050c8f1b/raw/213f8c713860b4b7ec90ff15f420e1e96826cc84/sample.json";
let member = Member::get(url).await.unwrap();
println!("{:#?}", member);
Ok(())
}
解決法
serde のアトリビュートを使えば、JSON のプロパティをそれと異なる名前のフィールドに格納できることがわかった( https://serde.rs/field-attrs.html#alias )。
たとえば、次のようにすれば良い。
struct Member {
#[serde(alias = "userName")]
user_name: String,
#[serde(alias = "userAge")]
user_age: i32,
#[serde(alias = "isPremiumNumber")]
is_premium_member: bool
}
応用
serde のアトリビュートを使えば、異なる名前のプロパティとフィールドを紐付けられることがわかった。
これを応用すれば、次のようなプロパティ名が日本語の JSON をうまく処理することができる。
{
"名前": "田中一郎",
"年齢": 30,
"有料会員": false
}
struct Member {
#[serde(alias = "名前")]
user_name: String,
#[serde(alias = "年齢")]
user_age: i32,
#[serde(alias = "有料会員")]
is_premium_member: bool
}
どうやら最新の Rust では識別子に Ascii 以外の文字も使えるらしいので、この場合そのままでもいいかもしれませんが。