【Flutter】json_serializable を使ってみた

# json_serializable とは

https://pub.dev/packages/json_serializable

こちら。

APIなどから取得したJSON文字列を、クラスに変換するとき、

```dart
    final jsonString = '{"id":123, "name":"THE BOOK", "price":1500, "author":{"id":999, "name":"sato"}}';

    final jsonStringMap1 = jsonDecode(jsonString);
    final author1 = Author(id: jsonStringMap1['author']['id'], name: jsonStringMap1['author']['name']);
    final person1 = Book(id: jsonStringMap1['id'], name: jsonStringMap1['name'], author: author1);
    print('id: ${person1.id}');
    print('name: ${person1.name}');
    print('price: ${person1.price}');
    print('author_id: ${person1.author?.id}');
    print('author_name: ${person1.author?.name}');
```

のように `jsonDecode()` でMap型に変換し、
コンストラクタで、取得したMap型と対象クラスの変数とのマッピングを行い、クラス化する。

この「取得したMap型と対象クラスの変数とのマッピングを行い」部分のコードを自動生成してくれるのが、`json_serializable` 。
(例として Map型をクラスに変換する部分を扱うが、クラスをMap型に変換する部分のマッピングにも対応している。)

# 使い方

### pubspec.yaml にパッケージを追加

```yaml
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.4
  json_serializable: ^6.7.0
```

`json_annotation` のアノテーションを使用して、 `build_runner` で生成するので3種類必要。

### 値を格納するクラスを用意

```dart
import 'package:flutter_json_serializable/utils/author.dart';
import 'package:json_annotation/json_annotation.dart';

part 'book.g.dart';

@JsonSerializable()
class Book {
    final id;
    final name;
    final price;
    final Author? author;

    Book({this.id, this.name, this.price, this.author});

    factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json);

    Map<String, dynamic> toJson() => _$BookToJson(this);
}
```

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

part 'author.g.dart';

@JsonSerializable()
class Author {
    final id;
    final name;

    Author({this.id, this.name});

    factory Author.fromJson(Map<String, dynamic> json) => _$AuthorFromJson(json);

    Map<String, dynamic> toJson() => _$AuthorToJson(this);
}
```

上から説明
* `import 'package:json_annotation/json_annotation.dart';` で json_annotation をインポート
* `part '【ファイル名を「.g.dart」拡張子にしたもの】';` を記載して、生成されるファイルと紐付ける
(「ファイル名を「.g.dart」拡張子にした」ファイル名で生成される)
* `@JsonSerializable()` で生成対象のクラス、という目印をつける
* 必要な変数とコンストラクタを記載
    * 基本は変数名とJSONのキー文字列がイコール
        * `build.yaml` や `JsonKey` を使用して別の文字列とのマッピングにすることもできるが割愛
    * 型を指定しないとJSON文字列から直接変換しようとするので、Bookクラスのauthorのみちゃんと型(Author)を指定している
* `factory 【クラス名】.fromJson(Map<String, dynamic> json) => _$【クラス名】FromJson(json);` を記載することでMap型からクラスへのマッピングメソッドを生成
    * `【クラス名】.fromJson(【Map型】)` をコンストラクタとして使用できる
* `Map<String, dynamic> toJson() => _$【クラス名】ToJson(this);` を記載することでクラスからMap型へのマッピングメソッドを生成
* 今回のように階層が複数ある場合は、階層ごとにマッピング用のクラスが必要となる

:warning: 生成後のファイルを参照してる部分もあるのでこの時点でエラーが出てるのは気にせず次に進む

### コード生成

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

これを叩くことにより、

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

part of 'book.dart';

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

Book _$BookFromJson(Map<String, dynamic> json) => Book(
      id: json['id'],
      name: json['name'],
      price: json['price'],
      author: json['author'] == null
          ? null
          : Author.fromJson(json['author'] as Map<String, dynamic>),
    );

Map<String, dynamic> _$BookToJson(Book instance) => <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'price': instance.price,
      'author': instance.author,
    };
```

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

part of 'author.dart';

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

Author _$AuthorFromJson(Map<String, dynamic> json) => Author(
      id: json['id'],
      name: json['name'],
    );

Map<String, dynamic> _$AuthorToJson(Author instance) => <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
    };
```

のようなメソッドが自動生成されるので、

```dart
    final jsonString2 = '{"id":123, "name":"THE BOOK", "price":1500, "author":{"id":999, "name":"sato"}}';

    final person2 = Book.fromJson(jsonDecode(jsonString2));
    print('id: ${person2.id}');
    print('name: ${person2.name}');
    print('price: ${person2.price}');
    print('author_id: ${person2.author?.id}');
    print('author_name: ${person2.author?.name}');
```

のように使用できる。