【Flutter】JSONをデコードする

この記事は [Flutter Advent Calendar 2018 \- Qiita](https://qiita.com/advent-calendar/2018/flutter) の20日目の記事です。

## 概要
FlutterにおけるJSONのデコード（エンコード）についてまとめます。

## まとめ
小規模なプロジェクトでは手動デコードでもいいかもしれませんが、中規模以上のプロジェクトではコードジェネレーター（[json_serializable](https://pub.dartlang.org/packages/json_serializable)）を使ったほうが良さそうです。

## やり方
### 手動デコードの場合
Flutterはデフォルトで `dart:convert` というパッケージが入っているので
[JsonCodec class](https://api.dartlang.org/stable/2.1.0/dart-convert/JsonCodec-class.html) で簡単にシリアライズできます。

```json:sample.json
{
  "name": "John Smith",
  "email": "john@example.com"
}
```

```dart:main.dart
Map<String, dynamic> user = json.decode(json);

print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
```

`json.decode()` の戻り値の型は `Map<String, dynamic>` なので実行するまでJSONのValueがどの型になるかは分かりません。
また、デコード処理するロジックにキー名を書かないといけません。そのままStringで書いているのでtypoする可能性があります。

### モデルクラスを使う場合

`User` というモデルクラスを用意し `User.fromJson()` / `User.toJson()` を定義します。

```dart:user.dart
class User {
  final String name;
  final String email;

  User(this.name, this.email);

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'];

  Map<String, dynamic> toJson() =>
    {
      'name': name,
      'email': email,
    };
}
```

```dart:main.dart
Map userMap = json.decode(json);
var user = new User.fromJson(userMap);

print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
```

エンコード・デコードのロジックをモデルクラスに内包することができました。
呼び出し側では`name`と`email`をStringではなくUserクラスのフィールドを参照しているので、typoしていた場合はコンパイルエラーで気がつくことができます。

### コードジェネレーターライブラリを使う場合
`pubspec.yaml`に`json_annotation`と`json_serializable`を追加します。

```yaml:pubspec.yaml
dependencies:
  json_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0
```

`User`クラスを定義して`JsonSerializable`アノテーションを付与します。

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

// ジェネレートされたクラスからUserクラスのprivateメンバ変数にアクセスするため
part 'user.g.dart';

@JsonSerializable()
class User {
  User(this.name, this.email);

  String name;
  String email;

  // _$UserFromJsonが生成される
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  // _$UserToJsonが生成される
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
```

FlutterプロジェクトのRootでコマンドラインで以下を実行。（）

```shell
flutter packages pub run build_runner build
```

実行後、コードが生成されるので前述したやり方と同様にエンコードできます。

```dart:main.dart
Map userMap = json.decode(json);
var user = User.fromJson(userMap);
```

エンコード・デコードのロジックをコードジェネレーターに移譲することで、`'name'`と`'email'`を書く必要がなくなりました。

#### 補足
JSONのキーと別名でモデルクラスのメンバ変数を定義した場合（例えばキーがsnake_caseでメンバ変数名をlowerCaseにしたい場合）は`JsonKey`アノテーションをつけることでJSONのキー名を指定できます。

```dart
@JsonKey(name: 'first_name')
final int firstName;
```

以下のコマンドをFlutterプロジェクトのRootで実行することで、変更を検知してコード生成を自動化することもできます。

```shell
flutter packages pub run build_runner watch
```

## 参考
- [JSON and serialization \- Flutter](https://flutter.io/docs/development/data-and-backend/json)
- [json\_serializable \| Dart Package](https://pub.dartlang.org/packages/json_serializable)
- [json\_serializable/example\.dart at master · dart\-lang/json\_serializable · GitHub](https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart)