RustでAPIのデーター(Json)をDeserializeする時に型が定まらず困ったことについて
はじめに
本記事はRustでWEBアプリケーションを作る際、reqwestでAPIデーター取得後JsonをDeserializeする際、型関連でハマったことをまとめています。
対象者
この記事は下記のような人を対象にしています。
- Rustの初学者
- 型にまだなれていない方
結論
「serde_json::Value」使うといい感じにDeserializeできました。
遭遇した問題
-「reqwest」を利用してとあるAPIからデーターを取得し、「serde」でStructにDeserializeしようとしたら、型があってないよ!とRustコンパイラーに怒られる。
- このときに定義していたStruct
#[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] struct User { cycle: String, eol: bool, latest: String, latest_release_date: String, release_date: String, lts: bool, }
- エラー内容
thread 'actix-rt|system:0|arbiter:1' panicked at 'called `Result::unwrap()` on an `Err` value: Error("invalid type: boolean `true`, expected a string", line: 1, column: 2450)'
- APIの仕様を確認してみたところエラーが発生する項目のリターン型は、「string or boolean」とある。
- ここからが悩みの始まり。。「Rustって同じフィールドに複数の型をつかえるのかなぁ」。
とりあえずググる。
「reqwest」を利用してAPIからデーターを取得するのは、他の方たちもたくさんやっているはずと思い、とりあえずググる。。。
けどなかった、利用サンプルなど確認しても、Deserializeの際は、固定された型でやっているものしか見つからず、みんなどうやっているのだろう。。
Deserializeを辞める。
serde_jsonのAPI文書に未定義タイプのJson利用方法が乗っていたのでこちらの方法を使ってみた。
https://docs.rs/serde_json/latest/serde_json/index.html#operating-on-untyped-json-values
- 結果
うまくJsonデーターは取れる。けど、やはりStructでデーター管理をしたい。。
「serde_json::Value」
API文書を読んているうちにserde_jsonで使っているデーター型はすべて「serde_json::Value」enum を使っていることに気づきました。英語だったから気づくのが遅かった。。
https://docs.rs/serde_json/latest/serde_json/value/enum.Value.html
じゃこれを使えば、うまくいくのではと思いためす。
- 修正したStruct
struct User { cycle: String, eol: serde_json::Value, latest: String, latest_release_date: String, release_date: String, lts: serde_json::Value, }
- コンソール出力
[User { cycle: "7.0", eol: Bool(false), latest: "7.0.4.2", latest_release_date: "2023-01-24", release_date: "2021-12-15", lts: Bool(false) }, User { cycle: "6.1", eol: Bool(false), latest: "6.1.7.2", latest_release_date: "2023-01-24", release_date: "2020-12-09", lts: Bool(false) }, User { cycle: "6.0", eol: String("2023-06-01"), latest: "6.0.6.1", latest_release_date: "2023-01-17", release_date: "2019-08-16", lts: Bool(false) }, User { cycle: "5.2", eol: String("2022-06-01"), latest: "5.2.8.1", latest_release_date: "2022-07-12", release_date: "2018-04-09", lts: Bool(false) }, User { cycle: "5.1", eol: String("2019-08-25"), latest: "5.1.7", latest_release_date: "2019-03-27", release_date: "2017-04-27", lts: Bool(false) }, User { cycle: "5.0", eol: String("2018-04-09"), latest: "5.0.7.2", latest_release_date: "2019-03-13", release_date: "2016-06-30", lts: Bool(false) }, User { cycle: "4.2", eol: String("2017-04-27"), latest: "4.2.11.3", latest_release_date: "2020-05-15", release_date: "2014-12-19", lts: Bool(false) }]
Deserializeできた!
Desirializeできたから値を取り出してみよう。
serde_jsonでは値を取り出せるメソッドも用意されていたのでそれらを利用する。
とりあえずループ回してPrintしてみる
- コード
&json_body.iter().for_each(|a| { println!( "Cycle: {}, eol: {:?}", a.cycle, a.eol.as_bool().unwrap_or_default() ); println!( "Cycle: {}, eol: {:?}", a.cycle, a.eol.as_str().unwrap_or_default() ); });
- 出力
Cycle: 7.0, eol: false Cycle: 7.0, eol: "" Cycle: 6.1, eol: false Cycle: 6.1, eol: "" Cycle: 6.0, eol: false Cycle: 6.0, eol: "2023-06-01" Cycle: 5.2, eol: false Cycle: 5.2, eol: "2022-06-01" Cycle: 5.1, eol: false Cycle: 5.1, eol: "2019-08-25" Cycle: 5.0, eol: false Cycle: 5.0, eol: "2018-04-09" Cycle: 4.2, eol: false Cycle: 4.2, eol: "2017-04-27"
おわりに
まだまだ、Rustの型システムの理解度が浅いことを実感しました。
当初は、型が定まらない=ゲネリックタイプという初歩的な考えしか持てずに時間を無駄にしました。
やはり、ちゃんと言語仕様を理解するのは大事ですね。
この記事が、私のようなRust初学者にお役に立てると幸いです。
もし、「こんなやり方もあるよ~」という方は、ぜひご教授いただけると助かります。