JSON Serialization in Dart ~json_serializableの巻~

DartでJSONをシリアライズ・デシリアライズする方法は大きく分けて2つあります。

1. シリアライズ・デシリアライズ処理を自分で書く
2. シリアライズ・デシリアライズ処理のコードを作成してくれるライブラリを使う

この記事は2の手法についての記事です。
今回紹介する[json_serializable](https://github.com/dart-lang/json_serializable)の他にも[built_value](https://github.com/google/built_value.dart)などがありますが、この記事では割愛します。

# json_serializable

json_serializableはDart公式が提供しているライブラリで、その名の通りJSONのシリアライズ・デシリアライズを助けてくれるものです。
普通にやると自分で書かなければならない面倒で単純なシリアライズ・デシリアライズ部分の処理を、[build_runner](https://github.com/dart-lang/build/tree/master/build_runner)を使って自動で生成してくれます。

今回はシリアライズにフォーカスして使い方を解説していきます。

## 1. モデルクラスをjson_annotationが提供するアノテーションを使って作る
```dart
import 'package:json_annotation/json_annotation.dart';

@JsonSerializable(createToJson: false)
class CustomerUser {
  final int id;
  final String nickname;
  final String thumbnail;

  CustomerUser({this.id, this.nickname, this.thumbnail});
}
```

`@JsonSerializable`と`@JsonKey`があれば大体カバーできます。

## 2. build_runnerを使って`*.g.dart`を作成
[build_runner](https://github.com/dart-lang/build/tree/master/build_runner)というライブラリを使います。
このライブラリがcode generationを担います。

`run build_runner build`を実行することで、1で作ったクラスに対応するファイルを自動で作ってくれます。
例えば、1で作ったクラスが`customer_user.dart`に書かれていたとすれば、`customer_user.g.dart`を作ってくれます。

```bash
$ pub run build_runner build
```

Flutterの場合は以下です。

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

成功すれば以下のようなファイルが作られます。

```dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'customer_user_model.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

CustomerUser _$CustomerUserFromJson(Map<String, dynamic> json) =>
    new CustomerUser(
        id: json['id'] as int,
        nickname: json['nickname'] as String,
        thumbnail: json['thumbnail'] as String);
```

今回の例はシンプルなモデルクラスなので、作成されるコードもシンプルになります。
1で設定したアノテーションによって内容は変わります。

## 3. 生成されたコードをモデルの各種値に設定する
生成されたコードは別ファイルにあるので、それらを使用するには以下をモデルクラスがあるファイルの方に追加する必要があります。

```dart
part 'customer_user_model.g.dart';
```

今回の例では`_$CustomerUserFromJson`が生成されたのでこれをfactoryコンストラクタに設定します。

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

クラス全体は以下になります。

```dart
import 'package:json_annotation/json_annotation.dart';

part 'customer_user_model.g.dart';

@JsonSerializable(createToJson: false)
class CustomerUser {
  final int id;
  final String nickname;
  final String thumbnail;

  CustomerUser({this.id, this.nickname, this.thumbnail});

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

この程度であれば自分で書いてもさほど変わらないかもですが、複雑になっていけばよりライブラリの便利さがわかると思います（例が微妙ですみません。。）。

## おまけTips

### JSON keyと変数名が異なる場合
上の例ではJSONのkeyと変数名が同じである前提ですが、異なる場合は多々あるかと思われます。
そう行った場合は`@JsonKey`の`name`を指定することで解決できます。

```dart
@JsonKey(name: 'created_at')
final DateTime createdAt;
```

### パース処理を変えたい場合
例えば、DateTimeとして変数が欲しい場合、普通にDateTimeの変数を定義すると生成されるコードでは以下のようなパース処理になります。

```dart
DateTime.parse(json['created_at'] as String)
```

DateTime.parseではパースできる型が限られています。
パースできない文字列が返って来たらこの処理は失敗してしまうので、処理を変える必要があります。
こういった場合に使えるのが、`@JsonKey`の`fromJson`です。
これにFunctionを与えるとパース処理がそれに変更されます。

```dart
@JsonKey(name: 'year_month', fromJson: _parseYearMonth)
final DateTime yearMonth;
```

```dart
DateTime _parseYearMonth(dynamic value) => DateFormat('yyyy-MM').parse(value);
```

# 使ってみた感想
JSONのシリアライズ・デシリアライズにフォーカスしているため、シンプルでわかりやすく学習コストが低いのがいいなと思いました。
他にbuilt_valueも使ってみましたが、json_serializableより複雑かつ面倒だったので個人的にはjson_serializableの方が好きだなと思っています。
ただbuilt_valueの方が多機能ではあるので必ずしもこちらが良い！ということはないです。

# Further references
- [JSON serialization in Dart strong mode](https://aloisdeniel.github.io/flutter-json-serialization/)
- [JSON and serialization](https://flutter.io/json/)