今回は、JsonConverter
を使用して、Freezedで使えるカスタムコンバータを作成する方法を書いていきます。
初めに
例えばこんなFreezedのモデルクラスがあるとします
@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からパースができません。
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 というパッケージを使用します
実際にJsonConverter
で実装したものがこちら
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に変換する処理になっています。
では上記で実装したコンバータをどう使うのかというと、このようにします
@CityEnumConverter() City? city, // ← `@CityEnumConverter()`をつける
全体のコードが↓
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を実装したクラス()
と宣言するだけで自動的にコンバートしてくれるようになります。
実際に動かしてみます
void main() {
final jsonData = {
'name': 'rei',
'city': 'tokyo',
};
final profile = Profile.fromJson(jsonData);
print(profile);
final json = profile.toJson();
print(json);
}
ログ↓
I/flutter ( 9860): profile: Profile(name: rei, city: City.tokyo)
I/flutter ( 9860): json: {name: rei, city: tokyo}
と、ちゃんとそれぞれ変換されているのがわかりますね
このようにJsonConverter
を実装したクラスを作成すると、便利なカスタムコンバータができるよというお話でした
おまけ
今回紹介したJsonConverterなんですが、知っておくと便利なものがあるのでちょっと紹介しようと思います
それがこちら
TimestampConverter
これは何かというと、 Firestore のパッケージの中に入っているカスタムコンバータです。
実装見てみるとこんな感じ
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()
と宣言するだけです。
@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を使うともっと便利にできるよということを知っていただければ幸いです。