저는 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 깃허브 저장소에서 볼 수 있었어요. 알고 보니 이건 freezed의 문제라기보다는 json_serializable의 설정 문제였어요.
저는 build_runner를 사용하기 있기 때문에, 이 문제를 해결하기 위해서 플러터 프로젝트에 build.yaml
파일을 만들어 줬어요. 그런 다음 아래 내용을 채워 넣어주세요.
targets:
$default:
builders:
json_serializable:
options:
explicit_to_json: true
그러면 build_runner를 통해 watch 모드를 켜두었다면 알아서 generated 파일들이 삭제되고 다시 만들어질 거예요. 다시 toJson을 시도하면 아까와는 다르게 내부에 사용되는 객체도 json 형식으로 잘 바뀐 것을 볼 수 있어요!
이 문제와 상관없을 수 있지만, 저는 TimeSlot 안에서 플러터에서 내장된 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