source_genって?
source_genはDartのコード生成ツールです。どうしてそれが必要なのか、といったところから簡単に説明していきたいと思います。
JSON からクラスのインスタンスを作りたい
JSONなどのデータフォーマットから、クラスのインスタンスを生成したい時、それぞれのクラスにfactory Constructorを定義する必要があります。
Personクラスを作って見ていきましょう。
class Person {
final String name;
final int age;
Person(this.name,this.age);
}
void main(){
Person jon = new Person("jon",10);
print("${jon.name} is ${jon.age}");
/// jon is 10
}
Person情報が保存されている以下の様なJSONファイルがあるとします。
{"name" : "kate","age" : 17}
これを使ってPerson クラスのインスタンスを生成したい場合、次のような方法が考えられます。
import 'dart:convert' show JSON;
class Person {
final String name;
final int age;
Person(this.name,this.age);
Person.fromJson(Map jsonMap) : this(jsonMap["name"],jsonMap["age"]);
void printInfo(){
print("${this.name} is ${this.age}");
}
}
void main(){
/// (どうでもいいですがジョンってjonじゃなくてjohnですね)
Person jon = new Person("jon",10);
jon.printInfo();
/// jon is 10
var source = """{"name" : "kate","age" : 17}""";
Person kate = new Person.fromJson(JSON.encode(source));
kate.printInfo();
/// kate is 17
}
このように、Jsonからそのインスタンスを生成するためのコンストラクタを用意しておけば、いちいちクラスをつかう側でJSONをエンコードして new Person(json["name"],json["age"])
といった面倒なことをする必要が無いです。まあ当たり前の話なのですが。
しかし、このfromJsonコンストラクタ、いちいち作るのが非常に面倒です。
今例に上げたPersonクラスは、非常にシンプルな構造のクラスでしたから、JSONからのコンストラクタを書くのも簡単でした。しかし、クラスが持つ変数が多くなってくると、いちいち書いているのは相当の苦行ですし、ミスがあるかもしれないですし、第一時間がもったいないですよね。
そこで、JSONのようなある特定のデータフォーマットからクラスのfactoryコンストラクタを自動生成してくれるライブラリが求められているわけです。
dart:mirrorsによる問題
json_objectというパッケージがあります。これを使うと、JSONからインスタンスを簡単に作ることが出来ます。
import 'package:json_object/json_object.dart';
abstract class Person {
final String name;
final int age;
}
class PersonImpl extends JsonObject implements Person {
PersonImpl();
factory Person.fromJsonString(string) {
return new JsonObject.fromJsonString(string, new PersonImpl());
}
void printInfo(){
print("${this.name} is ${this.age}");
}
}
void main() {
Person person = new PersonImpl.fromJsontString("""{"name" : "kate","age" : 17}""");
print(person.age); // 17
}
これを使うと先ほど行った「いちいちfromJsonStringみたいなクラスを書く」といった問題は解決します。
が。
このライブラリはリフレクション(dart:mirrors)を使っています。ので、dart2jsしたときがつらいです。
リフレクション (dart:mirrors) を使ったライブラリを dart2js する - Qiita
上の記事にもあるように、dart:mirrorsを利用したコードをdart2jsすると吐出されるjsファイルの大きさがとんでもないことになります。Dart Dev Compilerによってどうなるかはイマイチわかりませんが、つかうのは得策とは言えないでしょう。
source_genによる解決
これらの問題を解決するために、「コードを自動生成しよう」と作られたのがsource_genです。
実際に使って見てみましょう。
まずpubspec.yamlに次のように追記して、source_genをプロジェクトで使えるようにしましょう。
dependencies:
source_gen: '>=0.4.5+1 <0.5.0'
pub get
を実行します。
プロジェクトディレクトリ直下にlibディレクトリを作成します。
そしてJsonから作りたいクラスを定義します。
library sample.person;
import 'package:source_gen/generators/json_serializable.dart';
@JsonSerializable()
class Person{
final String name;
final int age;
Person(this.name,this.age);
}
@JsonSerializable()
というannotationがクラスについてます。これは「このクラスはJsonからのコンストラクタを生成する」という意味を持ちます。詳しくは後述します。
実際にコード生成をするためのbuildファイルを書きます。
library sample.build_file;
import 'package:source_gen/generators/json_serializable_generator.dart' as json;
import 'package:source_gen/source_gen.dart';
main(List<String> args) async {
var msg = await build(args, const [
const json.JsonSerializableGenerator()
], librarySearchPaths: [
'lib'
]);
print(msg);
}
build.dartを実行します。
dart build.dart
すると、次のようなコードがlib直下に生成されます。
// GENERATED CODE - DO NOT MODIFY BY HAND
// 2015-12-05T04:02:53.498Z
part of test.person;
// **************************************************************************
// Generator: JsonSerializableGenerator
// Target: class Person
// **************************************************************************
Person _$PersonFromJson(Map json) => new Person(json['name'], json['age']);
abstract class _$PersonSerializerMixin {
String get name;
int get age;
Map<String, dynamic> toJson() => <String, dynamic>{'name': name, 'age': age};
}
あとは先ほど作ったPerson classにこれがつかえる用に書き加えればOKです。
library sample.person;
import 'package:source_gen/generators/json_serializable.dart';
part 'person.g.dart';
@JsonSerializable()
class Person exntends Object implements _$PersonSerializerMixin {
final String name;
final int age;
Person(this.name,this.age);
factory Person.fromJson(json) => _$PersonFromJson(json);
}
このようにして、Jsonからクラスのインスタンスを生成するメソッドを自動的に生成することができます。
build_system.dartというパッケージを使えば、codeを書きなおす度にbuild.dartを叩いてくれるので、いちいち実行する必要もありません。
今回はsource_genの中にサンプルとして作られているJsonSerializableGeneratorについて説明しましたが、自分でコード生成用のクラスを作れば、自由にコードを自動生成することができます。
サンプルとしてライブラリを作ってみました。参考にしてみてください。sh4869/csv_code_generator.dart - GitHub
時間があったら、上のライブラリを例にどのようにGeneratorを作るかを説明してみたいと思います。
最後に
Dartはその言語の作られた目的上Jsonなどのデータフォーマットを利用することが多い言語です。というかどの言語でも多い気がしますが。そのため、こういったコードの自動生成は求められていたにもかかわらず、dart:mirrorsの問題によって解決することが出来ていませんでした。source_genはこういった問題を解決してくれるライブラリとなりうるので、今後もwatchして行きたいと思っています。