エキサイトの武藤です。
Flutter 開発を通して、freezed でのJSONの扱いに慣れてきました。
今回は freezed, json_annotation パッケージの基本的な使い方やTIPSをまとめていきます。
@JsonKey アノテーション
json_annotation パッケージの @JsonKey
アノテーションを使用して、JSONのキーとfreezedのオブジェクトのプロパティーをマッピングする挙動を設定できます。
name プロパティー
name
プロパティーでは、指定した文字列でJSONキーからプロパティーにマッピングできます。
下記のコードでは、author_id
を authorId
, author_name
を authorName
へのマッピングを記述しています。
@freezed class Book with _$Book { factory Book({ required String id, required String title, required String story, @JsonKey(name: 'author_id') required String authorId, @JsonKey(name: 'author_name') required String authorName, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); }
変換するフィールドが膨大な場合は、 @JsonSerializable
を使って一括で指定する方法もあります。
FieldRename.snake
を指定して、スネークケースからマッピングできるようになります。
@freezed class Book with _$Book { @JsonSerializable(fieldRename: FieldRename.snake) factory Book({ required String id, required String title, required String story, required String authorId, required String authorName, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); }
また、JSONのキー名を別名でプロパティーに設定したい場合にも使えます。
下記は、 contents
から story
に命名を変更しています。
@freezed class Book with _$Book { factory Book({ required String id, required String title, @JsonKey(name: 'contents') required String story, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); }
defalutValue プロパティー
defalutValue
プロパティーでは、初期値を設定できます。
APIレスポンスがnullで返ってきて、アプリケーションではnon nulllで扱いたいときに初期値を設定できます。
@freezed class Book with _$Book { factory Book({ required String id, required String title, @JsonKey(defaultValue: '') required String summary, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); }
fromJson プロパティー
fromJson
プロパティーでは、マッピングする際に実行したい関数を設定できます。関数はトップレベルで定義する必要があります。
具体的なケースとしては、String型のレスポンスをint型に変換したい場合が考えられます。
まず、下記のような変換メソッドを作ります。
int? stringToInt(String stringValue) { return int.tryParse(stringValue); }
freezedクラスでは、@JsonKey
アノテーションの fromJson
プロパティーにstringToInt()
メソッドを指定します。
@freezed class Book with _$Book { factory Book({ required String id, required String title, required String story, @JsonKey(fromJson: stringToInt) required int? price, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); }
APIレスポンスの型が不定でアプリ側で吸収する必要がある場合は、下記のようにdynamic型で受け取るメソッドを用意します。
int? toInt(dynamic jsonValue) { if ((jsonValue) is String) { return int.tryParse(jsonValue); } if ((jsonValue) is int) { return (jsonValue); } return null; }
カスタムゲッター、メソッドを追加する
freezedクラスにカスタムゲッターを設定します。
空のプライベートコンストラクタの追記がポイントになります。
@freezed class Book with _$Book { const Book._(); factory Book({ required String id, required String title, required String story, @JsonKey(name: 'author_id') required String authorId, @JsonKey(name: 'author_name') required String authorName, }) = _Book; factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json); String get summary => '${story.substring(0, 100)}...'; }
下記のように summary
にアクセスできるようになります。
final summary = book.summary;
build_runner watch でコード変更を監視
build_runner watch
を使って、変更を監視します。
flutter pub run build_runner watch --delete-conflicting-outputs
コードを修正して保存するタイミングでビルドされます。
体験的には build_runner build
で実行するよりも、build_runner watch
がスムーズです。
まとめ
基本的な内容ですが、freezed, json_annotation パッケージを使ったJSONから freezed オブジェクトへのマッピングについて説明しました。
アノテーションを利用して、変数名や型等の変換が細かく設定できます。
こちらの記事が参考になれば幸いです。
余談
先日行われたFlutterKaigi2022 にて、Dartのアップデートに関するセッションがありました。今後はDartのマクロ機能を使うことで、Flutterのコンパイルのタイミングでコードの自動生成が走るようになり、開発体験の向上が期待できそうです。アップデートが楽しみですね。