🧐

json_serializableでコードが自動生成されない

2022/07/20に公開

この問題、非常に悩みましたが、解決してみたら「えっ?そんなこと!?」みたいな感じでした。
おそらく、 flutter コマンドでプロジェクトの構成を自動生成した人は本現象は発生せず、dartのみを使って1からディレクトリ構成を考えた人は起こりがちかも、と感じました。(私だけ...?)

背景

少しずつではありますが、プライベートでDart言語(と今どきのアーキテクチャも合わせて)の勉強をするために、何かサンプルとなるアプリケーションを作りながらやってみよう!ということで、簡単なCLIツールを作っていました。

https://github.com/mltokky/jp_weather_information

アーキテクチャーやAPIとの通信の実装方法など、いろんな面で勉強になり、それなりにDartについては知識は身についたかなと思ったので、作ってみてよかったなと思いました。

DartでのJSONデコード/エンコード処理

本題のjson_serializableの話に入る前に、ライブラリを使わずデフォルトで含まれているdartのパッケージを使う場合についてを説明します。

ドキュメントは以下の通り。

https://api.dart.dev/be/175791/dart-convert/jsonDecode.html
https://api.dart.dev/be/175791/dart-convert/jsonEncode.html

使い方はこんな感じです。

JSONデコード(文字列→Dartオブジェクト)

decode.dart
import 'dart:convert';

void main(List<String> args) {
  var a = jsonDecode("{\"strValue\": \"Hello world!\", \"intValue\": 12345}");
  print(a["strValue"]);
  print(a["intValue"]);
}

出力例

$ dart run decode.dart
Hello world!
12345

JSONエンコード(Dartオブジェクト→文字列)

encode.dart
import 'dart:convert';

void main(List<String> args) {
  var jsonString = jsonEncode({"test1": "this is test1", "test2": 822373, "test3": false});
  print(jsonString);
}

出力例

$ dart run encode.dart
{"test1":"this is test1","test2":822373,"test3":false}

手軽にJSON文字列とオブジェクトの相互変換が可能ですが、 jsonDecode() メソッドで返ってくる値の型、 jsonEncode() で渡すオブジェクトの型は Map になっているため、キーを自分で指定して値を取り出し、その値を型チェックしたりキャストしたりなど、都度自分でやらないといけません。

そこで、JSON文字列⇔classのオブジェクト に相互変換ができる外部のライブラリを使って解消しよう!ということで、json_serializableを使うことにしました。

json_serializableについて

ちなみに、Kotlin/Javaでいう gsonmoshi のような、ライブラリのメソッドを呼んで一発でclassに変換する、みたいなライブラリは残念ながらdart(Flutter)にはないようです。

https://qiita.com/tfandkusu/items/dfea9d6c3258cbaf8921

https://docs.flutter.dev/development/data-and-backend/json#is-there-a-gsonjacksonmoshi-equivalent-in-flutter

上記のFlutter公式ドキュメントではたしかに The simple answer is no. となっており、公式でもはっきりと否定しています。
それはdart言語自体にリフレクションがないため、と解説されていますね。

そこで、それ以外のアプローチとしてGoogle自ら開発を行っている json_serializable を使うことにしました。

これは、JSON文字列⇔classのオブジェクトに相互変換するdartコードを自動生成し、それを利用するというものです。

基本的にはjson_serializableのパッケージのページを見れば、使い方などは分かると思います。

https://pub.dev/packages/json_serializable

問題発生!

とりあえず、先述したdartを使ってコマンドツールを作ってみよう!ということで、ディレクトリ構成もとりあえず自分で考えた構成でやっておりました。

実装したソースコードは /src 配下に配置し、それ以下をオニオンアーキテクチャーらしい構成にひとまずして、実装を進めていきました。

そして、API通信用のModelクラスを作成し、part xxxx.g.dart みたいな記述も用意し、 dart run build_runner build をターミナルで実行してみましたが...

$ dart run build_runner build
[INFO] Generating build script completed, took 187ms
[INFO] Reading cached asset graph completed, took 26ms
[INFO] Checking for updates since last build completed, took 545ms
[INFO] Running build completed, took 2ms
[INFO] Caching finalized dependency graph completed, took 14ms
[INFO] Succeeded after 21ms with 0 outputs (0 actions)

ん?なぜ 0 outputs (0 actions) なんだ???
ディレクトリ内を見ても、どこにも生成されず...

わけも分からず色々調べてみました。
ググってみたり、GithubのIssue確認したり、ソースコード読んでみたり...

そんなことをしていると、dartやflutterでのプロジェクトの標準ディレクトリ構成を解説している記事に行き着きました。
そこには、ソースコードは基本 /lib 下に格納する、というものが...

https://qiita.com/lacolaco/items/bc5288bb47d47e64a7e9

dart公式にも書かれていましたね。

https://dart.dev/tools/pub/package-layout

もしかして、この /lib 下しか見に行ってくれない...? と思い、ディレクトリ構成を変えてやってみました。

$ dart run build_runner build
[INFO] Generating build script completed, took 186ms
[INFO] Reading cached asset graph completed, took 27ms
[INFO] Checking for updates since last build completed, took 538ms
[INFO] Running build completed, took 505ms
[INFO] Caching finalized dependency graph completed, took 13ms
[INFO] Succeeded after 523ms with 4 outputs (4 actions)

今度はちゃんと生成してくれたみたいです!
念のため @JsonSerializable アノテーションを付けたdartファイルと同じディレクトリに xxxx.g.dart といったファイルが生成されているか確認しましたが、しっかりと存在しており、JSON文字列とdart上のオブジェクトとの相互変換する処理も自動生成されておりました。

言語ごとに標準ディレクトリ構成が用意されていたりするので、そのあたりはしっかりと公式ドキュメント等は見ておくべきだな、と感じました。

ちなみに、 /lib 以外のディレクトリにあるdartファイルについても検知してくれるオプションがあるのかが気になるので、手の空いたときに調べてみようかと思います。

Discussion