Flutterでdomainオブジェクトを作る際にfreezedとjson_serializableパッケージを使っています。ところが、あるクラスが別のクラスに依存しており、そのオブジェクトをtoJson関数でMapに変換した後、FirebaseのFirestoreに保存しようとすると以下のようなエラーが発生しました。
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: Instance of '_$TimeSlotImpl'
エラーが発生したWhenToMeetオブジェクトは以下の通りです。TimeSlotオブジェクトが問題になっていた状況でした。
@freezed
class WhenToMeet with _$WhenToMeet {
const factory WhenToMeet({
required WhenToMeetID id,
required String title,
required UserID creator,
required List<User> invitees,
required List<User> attendees,
required Map<DateTime, Set<TimeSlot>> availableTimes,
required Map<UserID, Map<DateTime, TimeSlot>> selectedTimes,
}) = _WhenToMeet;このエラーをデバッグするのは簡単ではありませんでした。_$TimeSlotImplはbuild_runnerで生成されたfreezed.dartファイルで作られ、g.dartファイルで参照されているオブジェクトでした。自分で作ったものではないため、なぜエラーが発生するのか不思議でした。そこでprint関数を使ってtoJsonの結果を確認してみました。

JSONであればオブジェクトの情報がないはずなのに、まるでtoStringを出力したかのようにTimeSlotオブジェクトとTimeOfDayオブジェクトの情報が表示されていて、おかしいと思いました。
そこで調べてみると、似たような問題をfreezedのGitHubリポジトリで見つけました。実はこれはfreezedの問題というよりもjson_serializableの設定の問題でした。
build_runnerを使っているので、この問題を解決するためにFlutterプロジェクトにbuild.yamlファイルを作成しました。その後、以下の内容を記述してください。
targets:
$default:
builders:
json_serializable:
options:
explicit_to_json: trueすると、build_runnerのwatchモードを有効にしている場合、自動的にgeneratedファイルが削除されて再生成されます。再度toJsonを試すと、先ほどとは違い、内部で使用されるオブジェクトもJSON形式にきちんと変換されていることが確認できます!

この問題とは直接関係ないかもしれませんが、TimeSlot内でFlutterに組み込みのTimeOfDayオブジェクトを使っており、これがtoJsonでうまくいかなかったため、JsonConverterを実装した_TimeOfDayConverterを以下のように使用しています。
@freezed
class TimeSlot with _$TimeSlot {
@Assert('start.compareTo(end) != 0', 'startはendより早くなければなりません。')
factory TimeSlot({
@_TimeOfDayConverter() required TimeOfDay start,
@_TimeOfDayConverter() required TimeOfDay end,
}) = _TimeSlot;A room without books is like a body without a soul.
— Marcus Tullius Cicero