【Flutter】【Freezed】JsonConverterを使った独自のカスタムコンバータを作る

今回は、`JsonConverter`を使用して、Freezedで使えるカスタムコンバータを作成する方法を書いていきます。

## 初めに

例えばこんなFreezedのモデルクラスがあるとします

```dart
@freezed
abstract class Profile with _$Profile {
  const factory Profile({
   String? name,
   String? city,
  }) = _Profile;

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

Jsonデータをパースするために `fromJson()` を用意しています

この時 `city`には **"tokyo", "osaka", "fukuoka"** といった特定の値が入るとします。

こんな時 `city` を文字列ではなく **Enum** で持ちたいと思ったことはありませんか？

単にcityの型をEnumにするだけだとJsonからパースができません。

```dart
enum City {
  tokyo,
  osaka,
  fukuoka,
}

@freezed
abstract class Profile with _$Profile {
  const factory Profile({
    String? name,
    City? city, // ←パースができない
  }) = _Profile;

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

そんな時に使えるのが`JsonConverter` です

## JsonConverterとは
JsonConverterとは、JsonConverterをインターフェースとしたクラスを作成することで、独自の `fromJson`、`toJson`を実装することができます。
実際に作ってみていきましょう

## JsonConverterを実装したカスタムコンバーター

今回のケースでは `Enum　→ String` `String → Enum` といった変換を行いたいので [enum_to_string](https://pub.dev/packages/enum_to_string) というパッケージを使用します

実際に`JsonConverter`で実装したものがこちら

```dart
enum City {
  tokyo,
  osaka,
  fukuoka,
}

class CityEnumConverter implements JsonConverter<City?, String?> {
  const EnumConverter();

  @override
  City? fromJson(String? json) => EnumToString.fromString(
        City.values,
        json ?? '',
      );

  @override
  String? toJson(City? object) =>
      object == null ? null : EnumToString.convertToString(object);
}
```

JsonConverterをインターフェースとして `CityEnumConverter` というものを実装しています

`fromJson`で**StringからEnum(City)**に、 `toJson`で**Enum(City)からString**に変換する処理になっています。

では上記で実装したコンバータをどう使うのかというと、このようにします

```dart
@CityEnumConverter() City? city, // ← `@CityEnumConverter()`をつける
```

全体のコードが↓

```dart
enum City {
  tokyo,
  osaka,
  fukuoka,
}

@freezed
abstract class Profile with _$Profile {
  const factory Profile({
    String? name,
    @CityEnumConverter() City? city, // ← `@CityEnumConverter()`をつける
  }) = _Profile;

  factory Profile.fromJson(Map<String, dynamic> json) =>
      _$ProfileFromJson(json);
}

class CityEnumConverter implements JsonConverter<City?, String?> {
  const EnumConverter();

  @override
  City? fromJson(String? json) => EnumToString.fromString(
        City.values,
        json ?? '',
      );

  @override
  String? toJson(City? object) =>
      object == null ? null : EnumToString.convertToString(object);
}
```

このように、一度実装してしまえば使いたいところで `@JsonConverterを実装したクラス()` と宣言するだけで自動的にコンバートしてくれるようになります。

実際に動かしてみます

```dart
void main() {
  final jsonData = {
    'name': 'rei',
    'city': 'tokyo',
  };

  final profile = Profile.fromJson(jsonData);
  print(profile);

  final json = profile.toJson();
  print(json);
}
```

ログ↓

```zsh
I/flutter ( 9860): profile: Profile(name: rei, city: City.tokyo)
I/flutter ( 9860): json: {name: rei, city: tokyo}
```

と、ちゃんとそれぞれ変換されているのがわかりますね

このように`JsonConverter`を実装したクラスを作成すると、便利なカスタムコンバータができるよというお話でした

## おまけ
今回紹介したJsonConverterなんですが、知っておくと便利なものがあるのでちょっと紹介しようと思います

それがこちら
`TimestampConverter`

これは何かというと、 **Firestore** のパッケージの中に入っているカスタムコンバータです。

実装見てみるとこんな感じ

```dart
class TimestampConverter implements JsonConverter<DateTime?, Timestamp?> {
  const TimestampConverter();

  @override
  DateTime? fromJson(Timestamp? json) => json?.toDate();

  @override
  Timestamp? toJson(DateTime? object) =>
      object == null ? null : Timestamp.fromDate(object);
}
```

見てわかる通り、Firestoreで日時を管理するときに利用する `Timestamp` をDartの`DateTime`に変換してくれるものになります。

使用方法は今回の記事で作成したCityEnumConverterと同じように変換させたいパラメータの先頭に `@TimestampConverter()` と宣言するだけです。

```dart
@freezed
abstract class Profile with _$Profile {
  const factory Profile({
    String? name,
    @CityEnumConverter() City? city,
    @TimestampConverter() DateTime? birthDate, // ←@TimestampConverter()をつける
  }) = _Profile;

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

このTimestampConverterを使えば、Firestoreのデータをパースしたい時、Timestampを考慮してゴニョゴニョとする必要がなくなります。

## 最後に
Flutterでモデルクラスを作成するときFreezedを使用することが多いと思います。
そんな中でJsonConverterを使うともっと便利にできるよということを知っていただければ幸いです。