flutterでjson

最近ぽちぽちされるようになった[Flutterの画面遷移](https://qiita.com/tatsu/items/38cd85efd93005b95af9)のサンプルソースを確認したところ、[dartson](https://pub.dartlang.org/packages/dartson)が使えなくなっており、[json_serializable](https://pub.dartlang.org/packages/json_serializable)に差し替えたので、その手順をまとめました。

> 他にも古いFlutterアプリを最新の環境でビルドして実行させるためにいくつか更新する必要がありましたが、それはまた別途まとめたいと思います。

##dartson

dartsonは開発中においてはmirrors（リフレクション）を使用しているが、deploy時には不要なのでdart2jsにやさしい、ということでちょっと前まで推されてました。
https://www.dartlang.org/articles/libraries/serialization

実際、使ってみると非常にシンプルで使い勝手がよかったと思います。

flutterは前々からmirrorsをサポートしていなかったとは思いますが、そのときは何も意識しておらず、単に使えていたから使ったにすぎないのですが、Flutter Beta 2の現時点（実際にはもっと前からだと思いますが未確認）で、正常には動作できなくなっています。

```
E/flutter ( 4126): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] error: import of dart:mirrors with --enable-mirrors=false
E/flutter ( 4126): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Dart_GetClosure expects argument 'library' to be non-null.
```

確かに、gitterのFlutterチャンネルで、Flutterの中の人がほかのパッケージを探せといってます。

> Michael Thomsen @mit-mit Dec 22 2017 16:28
Yes, mirrors are not supported so if the library actually depends on them, it wont work

> Michael Thomsen @mit-mit Dec 22 2017 16:31
I have looked for this specifically before, but generally options would be 1) look for another dart package, 2) write a dart package, or 3) write a small plugin that then does it on the platform side

##json_serializable

jsonパッケージはいくつかあるようですが、Dartは"Batteries included"なので、まずはDartチーム謹製を試すのが正しい姿勢かと思い、json_serializableに差し替えてみました。

なお、Flutter視点で書いてますが、参考にさせて頂いた[AngularDartでHTTP（REST）通信](https://qiita.com/hayassh/items/4d404396ac6a3a496cca)の通り、json_serializableはFlutterに限らず使用できます。

> 但し、ここ数ヶ月でコード生成手順が変わってます。めまぐるしい、、、。

###パッケージ追加

```yaml:pubspec.yaml
dependencies:
  # ...snip...
  json_annotation: ^0.2.3

dev_dependencies:
  # ...snip...
  build_runner: ^0.8.0
  json_serializable: ^0.5.0
```

###モデルクラス作成

```dart:lib/entity/library.dart
import 'package:json_annotation/json_annotation.dart';

part 'library.g.dart';

@JsonSerializable()
class Library extends Object with _$LibrarySerializerMixin {
  @JsonKey(name: 'systemid')
  final String systemId; // システムID
  @JsonKey(name: 'systemname')
  final String systemName; // システム名称
  @JsonKey(name: 'libkey')
  final String libKey; // システム毎の図書館キー
  @JsonKey(name: 'libid')
  final String libId; // 図書館のユニークID
  @JsonKey(name: 'short')
  final String shortName; // 略称
  @JsonKey(name: 'formal')
  final String formalName; // 正式名称
  @JsonKey(name: 'url_pc')
  final String urlPC; // PC版ウェブサイト
  final String address; // 住所
  final String pref; // 都道府県
  final String city; // 市町村
  final String post; // 郵便番号
  final String tel; // 電話番号
  @JsonKey(name: 'geocode')
  final String geoCode; // 位置情報
  final String category; // カテゴリー（下記参照）
  final String image; // 外観写真（現時点では空です）
  final String distance; // パラメータでgeocodeが指定されている場合、その地点からの距離(単位：km)

  Library(
      {this.systemId,
      this.systemName,
      this.libKey,
      this.libId,
      this.shortName,
      this.formalName,
      this.urlPC,
      this.address,
      this.pref,
      this.city,
      this.post,
      this.tel,
      this.geoCode,
      this.category,
      this.image,
      this.distance});

  factory Library.fromJson(Map<String, dynamic> json) => _$LibraryFromJson(json);
}
```

一部これから生成して参照する'クラスのファイル名.g.dart'のコードをpartで指定します。_$で参照しているのはこれから生成される名前です。当然ですが、この時点では存在していないので、IDEを使用しているとエラーで表示されます。

クラスは'_$クラス名SerializerMixin'をmixinする決まりです。

```dart
class Library extends Object with _$LibrarySerializerMixin {
```

factoryコンストラクタは'_$クラス名FromJson'ファンクションで生成する決まりです。

```dart
  factory Library.fromJson(Map<String, dynamic> json) => _$LibraryFromJson(json);
```

アノテーションは[json_annotation](https://pub.dartlang.org/packages/json_annotation)パッケージに依存しています。[example](https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart)のほうが参考になるかも。だいたい見たまんまですがドキュメントは[こちら](https://pub.dartlang.org/documentation/json_annotation/0.2.3/json_annotation/json_annotation-library.html)。

###コード生成

コマンドを実行し、'クラスのファイル名.g.dart'ファイルを生成します。

```
$ flutter packages pub run build_runner build
```

ちなみに、これはflutterからなので、通常のコマンドは```pub run build_runner build```になります。よくみればDart 2におけるただのbuildコマンドなので、安定版がリリースされる頃には手打ちせずともウラで勝手に走っていると思います。（←build_runnerがマストというわけでもなさそうなのでガセだったかな。JetBrains系IDEはWatcherで対応するとか？）

watcherを起動しておけば、ソースの更新を監視して勝手にbuildしてくれます。

```
$ flutter packages pub run build_runner watch
```

以下のファイルが同じディレクトリに生成されます。

```dart:lib/entity/library.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'library.dart';

// **************************************************************************
// Generator: JsonSerializableGenerator
// **************************************************************************

Library _$LibraryFromJson(Map<String, dynamic> json) => new Library(
    systemId: json['systemid'] as String,
    ...snip...
    distance: json['distance'] as String);

abstract class _$LibrarySerializerMixin {
  String get systemId;
  ...snip...
  String get distance;
  Map<String, dynamic> toJson() => <String, dynamic>{
        'systemid': systemId,
        ...snip...
        'distance': distance
      };
}
```

シリアライズされたJSON文字列からオブジェクトを生成する箇所は通常こんな感じかと。

```dart
  Library library = new Library.fromJson(json.decode(data) as Map<String, dynamic>);
```

但し、ここのケースでは配列が戻ってきますので、こんな感じになります。

```dart
  List<Library> libraries = (json.decode(data) as List).map((e) => new Library.fromJson(e)).toList();
```

あとはビルドして実行するだけです。

##参照

https://flutter.io/json/
[AngularDartでHTTP（REST）通信](https://qiita.com/hayassh/items/4d404396ac6a3a496cca)
[JSON serialization in Dart strong mode](http://aloisdeniel.github.io//flutter-json-serialization/)