freezed,JsonSerialiable
| topics | 500-모바일개발 501 Flutter 503 Dart |
| types | 도구 실습 |
| tags | |
| references | medium.com/@hasimyerlikaya/flutter-cu... velog.io/@leeeeeoy/Flutter-Freezed-%E... velog.io/@hongstone/Flutter-jsonseria... |
Freezed & JsonSerializable
Dart에서 불변 데이터 클래스와 JSON 직렬화를 위한 코드 생성 도구다.
왜 Freezed를 쓸까
// 직접 작성하면...
class User {
final String name;
final int age;
User({required this.name, required this.age});
User copyWith({String? name, int? age}) {
return User(name: name ?? this.name, age: age ?? this.age);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User && other.name == name && other.age == age;
}
@override
int get hashCode => name.hashCode ^ age.hashCode;
@override
String toString() => 'User(name: $name, age: $age)';
}
문제점: 필드가 많아지면 boilerplate가 엄청 늘어난다. 필드 추가할 때마다 copyWith, ==, hashCode 다 수정해야 한다.
설치
dependencies:
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
dev_dependencies:
build_runner: ^2.4.6
freezed: ^2.4.5
json_serializable: ^6.7.1
기본 사용법
Freezed 클래스 정의
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String name,
required int age,
String? email,
@Default(false) bool isActive,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
코드 생성 명령어
# <span id="한번만-빌드"></span>한번만 빌드
dart run build_runner build
# <span id="또는-flutter-프로젝트"></span>또는 flutter 프로젝트
flutter pub run build_runner build
# <span id="파일-변경-감지하여-자동-빌드"></span>파일 변경 감지하여 자동 빌드
dart run build_runner watch
# <span id="충돌-시-삭제-후-재빌드"></span>충돌 시 삭제 후 재빌드
dart run build_runner build --delete-conflicting-outputs
사용 예시
// 생성
final user = User(name: 'John', age: 30);
// copyWith
final updated = user.copyWith(age: 31);
// 비교 (== 자동 구현)
print(user == User(name: 'John', age: 30)); // true
// toString (자동 구현)
print(user); // User(name: John, age: 30, email: null, isActive: false)
@JsonKey 사용법
필드명 매핑
@freezed
class User with _$User {
const factory User({
@JsonKey(name: 'user_name') required String userName,
@JsonKey(name: 'created_at') required DateTime createdAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
기본값 설정
@freezed
class Config with _$Config {
const factory Config({
@JsonKey(defaultValue: 10) required int timeout,
@JsonKey(defaultValue: []) required List<String> tags,
}) = _Config;
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
}
필드 무시
@freezed
class User with _$User {
const factory User({
required String name,
@JsonKey(includeFromJson: false, includeToJson: false)
String? tempData,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Custom JsonConverter
DateTime이나 enum 같은 커스텀 타입 변환에 사용한다.
class DateTimeConverter implements JsonConverter<DateTime, String> {
const DateTimeConverter();
@override
DateTime fromJson(String json) => DateTime.parse(json);
@override
String toJson(DateTime object) => object.toIso8601String();
}
@freezed
class Event with _$Event {
const factory Event({
required String title,
@DateTimeConverter() required DateTime date,
}) = _Event;
factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
}
Union Types / Sealed Class
@freezed
sealed class ApiResponse with _$ApiResponse {
const factory ApiResponse.success(User user) = Success;
const factory ApiResponse.error(String message) = Error;
const factory ApiResponse.loading() = Loading;
}
// 사용
Widget build(ApiResponse response) {
return response.when(
success: (user) => Text(user.name),
error: (msg) => Text(msg),
loading: () => CircularProgressIndicator(),
);
}
편의 설정 (build.yaml)
프로젝트 루트에 build.yaml 생성:
targets:
$default:
builders:
json_serializable:
options:
# snake_case → camelCase 자동 변환
field_rename: snake
# null이면 JSON에서 제외
include_if_null: false
관련 문서
- annotion - Dart 어노테이션
- dart 3.0 - Sealed Class
- live templete - Live Template으로 빠르게 작성