👌

【Flutter】freezedでフィールドに自分で作った型やDateTimeがあるとfromJsonが作れない問題

2021/11/19に公開

FlutterでFreezedを使ってコード生成を行っている場合、自作クラスやDateTimeをフィールドに含めてfromJsonを生成しようとすると、目的の型に正しくキャストされず失敗してしまいます。
この記事では、そのような問題の解決策を紹介します。

freezedや一緒に使うことが多いriverpodstate_notifierの使い方については以下の記事をご覧ください。

https://qiita.com/tokkun5552/items/70a8f18abbae4bb06aa3

問題

繰り返しになりますが下記の場合のように、freezedで定義するフィールドに自分で作った型やDateTimeを宣言すると、fromJsonを生成したときに上手く生成してくれません。

todo_id.dart

class TodoId with _$TodoId {
  const factory TodoId([
    String? value,
  ]) = _TodoId;
}
  • 使っている側はこんな感じ
todo_item.dart

class TodoItem with _$TodoItem {
  const factory TodoItem({
    required TodoId id,
    required Title title,
    required Detail detail,
    required DateTime createdAt,
  }) = _TodoItem;

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

解決方法

下記のように、JsonConverterを実装したクラスを作り、変換する処理を記載します。

todo_id.dart

class TodoId with _$TodoId {
  const factory TodoId([
    String? value,
  ]) = _TodoId;
}

class TodoIdConverter implements JsonConverter<TodoId, String> {
  const TodoIdConverter();

  
  TodoId fromJson(String? value) {
    return TodoId(value);
  }

  
  String toJson(TodoId todoId) {
    return todoId.value ?? '';
  }
}

あとは、アノテーション付きで各フィールドに宣言します。

todo_item.dart

class TodoItem with _$TodoItem {
  const factory TodoItem({
    () required TodoId id,
    () required Title title,
    () required Detail detail,
    () required DateTime createdAt,
  }) = _TodoItem;

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

これを行うと、以下のようにfreezedで生成されるtoJson/fromJsonのメソッド内でConverterをつかって変換してくれるようになります。

todo_item.g.dart
_$_TodoItem _$$_TodoItemFromJson(Map<String, dynamic> json) => _$_TodoItem(
      id: const TodoIdConverter().fromJson(json['id'] as String),
      title: const TitleConverter().fromJson(json['title'] as String),
      detail: const DetailConverter().fromJson(json['detail'] as String),
      createdAt:
          const DateTimeConverter().fromJson(json['createdAt'] as String),
    );

Map<String, dynamic> _$$_TodoItemToJson(_$_TodoItem instance) =>
    <String, dynamic>{
      'id': const TodoIdConverter().toJson(instance.id),
      'title': const TitleConverter().toJson(instance.title),
      'detail': const DetailConverter().toJson(instance.detail),
      'createdAt': const DateTimeConverter().toJson(instance.createdAt),
    };

ちなみに、DateTimeConverterは以下のように実装しています。

date_time_converter.dart
class DateTimeConverter implements JsonConverter<DateTime, String> {
  const DateTimeConverter();

  
  DateTime fromJson(String json) {
    return DateTime.parse(json).toLocal();
  }

  
  String toJson(DateTime dateTime) {
    return dateTime.toLocal().toString();
  }
}

参考

https://qiita.com/tetsufe/items/3c7d997f1c13c659628c#comment-5ce93b14ddfbc9ee6428

Discussion