長くAndroidアプリエンジニアをやってきて最近Flutterを始めました。その中でJSONパースしてをオブジェクトに変換するやりかたについてAndroidと大きく違うと思ったので紹介します。内容としてはほぼFlutter公式ドキュメントのJSON and serializationページのものになります。
Androidの場合
このような名刺情報のJSONがあったとして
(乱数で作成した疑似個人情報です)
{
"id": 1,
"name": "小林 美穂",
"company": "田中繊維株式会社",
"division": "営業部",
"title": "係長"
}
Kotlinで同じフィールド名と型を持つクラスを作成すると
data class Card(val id: Int, val name: String, val company: String,
val division: String, val title: String)
GSONでパースできます。
val card = gson.fromJson(json, Card::class.java)
Javaでもやりかたは同様です。
FlutterにはGSONと同等の物が無い
FlutterでもGSON相当の物があると思っていましたが無かったです。
FlutterでのJSONデコードのやりかたはFlutter公式ドキュメントのJSON and serializationページに載っていました。親切にもIs there a GSON/Jackson/Moshi equivalent in Flutter?節があり、The simple answer is no.
と私と同じことを考えていた人を適切に導いています。
Flutterにはリフレクションが無い
その節ではGSON/Jackson/Moshiと同等の物が無い理由としてリフレクションを無効化しているからと説明しています。リフレクションとは実行時にオブジェクトの構造を得ることです。例えばこのようにクラスのフィールド一覧を実行時に得ることができます。
Card::class.java.declaredFields.map {
println(it.name)
}
Flutterはリリースビルド時に不要コード除去を行いアプリサイズを最適化します。しかしリフレクションは不要コード除去に邪魔になる機能です。リフレクションはすべてのコードが存在することを意図しているため、どのコードが不要なのか分からなくなってしまいます。
そうならば、AndroidのProGuardみたくリフレクションの対象になっているクラスを明示的に指定すれば良いのではと思うのですが、そういう設計思想では無いようです。
FlutterでのJSONデコードのやりかた
FlutterでJSONデコードを行う方法は2つあります。
- Map クラスのインスタンスから手動でパースする
- ソースコード生成を行う
手動でパースする
// convertライブラリをインポートする
import 'dart:convert';
// JSON文字列をMap<String, dynamic>に変換
final map = json.decode(jsonString);
final card = Card();
card.id = map['id'];
card.name = map['name'];
card.company = map['company'];
card.division = map['division'];
card.title = map['title'];
一見面倒ですが、これで良いと思います。
サーバー側から取得したデータはデータベースのスキーマに基づいていて画面表示用には使いにくいケースがあり、その場合は画面表示用の使いやすい形に手動で変換します。手動で変換するならば元がオブジェクトでもマップでも、記述量はそれほど変わらないと思います。
ソースコード生成を行う
pubspec.yamlファイルを編集して必要なライブラリを定義します。
dependencies:
json_annotation: 2.4.0
dev_dependencies:
build_runner: 1.5.2
json_serializable: 3.0.0
このようにクラスを定義します。
// json_annotationライブラリを使う
import 'package:json_annotation/json_annotation.dart';
// 自動生成されたメソッドにこのクラスからアクセスできるようにする。
part 'card.g.dart';
// アノテーションをつける
@JsonSerializable()
class Card {
Card(this.id, this.name, this.company, this.division, this.title);
int id;
String name;
String company;
String division;
String title;
// MapからCardへの変換
factory Card.fromJson(Map<String, dynamic> json) =>
_$CardFromJson(json) /*自動生成される*/;
// CardからMapへの変換
Map<String, dynamic> toJson() => _$CardToJson(this) /*自動生成される*/;
}
コマンドラインで以下のコマンドを実行します。
flutter pub run build_runner build
このようなコードが生成されます。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'card.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Card _$CardFromJson(Map<String, dynamic> json) {
return Card(
json['id'] as int,
json['name'] as String,
json['company'] as String,
json['division'] as String,
json['title'] as String);
}
Map<String, dynamic> _$CardToJson(Card instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'company': instance.company,
'division': instance.division,
'title': instance.title
};
このように使うことができます。
final map = json.decode(jsonString);
final card = Card.fromJson(map);
感想
Flutterを使うときはAndroidのやりかたに固執してはいけないと思いました。