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

관련 문서