DevToolBoxFREE
BlogAdvertise

JSON to Dart: Flutter 모델 클래스 완전 가이드

10분 읽기by DevToolBox

JSON을 Dart 모델 클래스로 변환하는 것은 Flutter 개발에서 가장 일반적인 작업 중 하나입니다. REST API에서 데이터를 가져오든 로컬 설정을 읽든, JSON 데이터를 안전하게 파싱하고 사용하려면 잘 구조화된 Dart 클래스가 필요합니다.

모델 클래스가 필요한 이유

  • 타입 안전성 — 런타임이 아닌 컴파일 타임에 오류 포착
  • IDE 자동완성 및 리팩토링 지원
  • 프론트엔드와 백엔드 간의 명확한 데이터 계약
  • 쉬운 테스트 및 유지보수

기본 JSON-Dart 타입 매핑

JSON 타입Dart 타입예시
stringString"hello" → "hello"
number (int)int42 → 42
number (float)double3.14 → 3.14
booleanbooltrue → true
nullNull / dynamicnull → null
arrayList<T>[1,2,3] → [1,2,3]
objectMap / Class{} → User()

수동 모델 클래스 (fromJson / toJson)

가장 직접적인 방법은 모델 클래스를 수동으로 작성하는 것입니다:

// JSON input
{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "isActive": true,
  "score": 95.5
}

// Dart model class
class User {
  final int id;
  final String name;
  final String email;
  final bool isActive;
  final double score;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.isActive,
    required this.score,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      isActive: json['isActive'] as bool,
      score: (json['score'] as num).toDouble(),
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'isActive': isActive,
      'score': score,
    };
  }
}

중첩 객체 처리

JSON에 중첩 객체가 포함된 경우 각 레벨에 별도의 Dart 클래스를 만듭니다:

// JSON with nested object
{
  "id": 1,
  "name": "John",
  "address": {
    "street": "123 Main St",
    "city": "Springfield",
    "zipCode": "62701"
  }
}

// Dart classes
class Address {
  final String street;
  final String city;
  final String zipCode;

  Address({required this.street, required this.city, required this.zipCode});

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      street: json['street'] as String,
      city: json['city'] as String,
      zipCode: json['zipCode'] as String,
    );
  }

  Map<String, dynamic> toJson() => {
    'street': street, 'city': city, 'zipCode': zipCode,
  };
}

class User {
  final int id;
  final String name;
  final Address address;

  User({required this.id, required this.name, required this.address});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      address: Address.fromJson(json['address'] as Map<String, dynamic>),
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id, 'name': name, 'address': address.toJson(),
  };
}

리스트와 배열 처리

JSON 배열은 Dart List 타입에 매핑됩니다:

// JSON with arrays
{
  "name": "John",
  "hobbies": ["coding", "reading", "gaming"],
  "orders": [
    {"id": 1, "product": "Laptop", "price": 999.99},
    {"id": 2, "product": "Mouse", "price": 29.99}
  ]
}

// Dart
class User {
  final String name;
  final List<String> hobbies;
  final List<Order> orders;

  User({required this.name, required this.hobbies, required this.orders});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      hobbies: List<String>.from(json['hobbies'] as List),
      orders: (json['orders'] as List)
          .map((e) => Order.fromJson(e as Map<String, dynamic>))
          .toList(),
    );
  }
}

Null Safety 모범 사례

Dart의 sound null safety에서는 nullable 필드를 올바르게 처리해야 합니다:

  • 항상 존재해야 하는 필드에는 `required` 키워드 사용
  • nullable 타입에는 `?` 접미사 사용 (예: `String?`)
  • fromJson에서 `??` 연산자로 기본값 제공
  • 값이 초기화될 것이 확실한 경우에만 `late` 키워드 사용
class User {
  final int id;
  final String name;
  final String? bio;        // nullable
  final String avatarUrl;   // with default

  User({
    required this.id,
    required this.name,
    this.bio,
    this.avatarUrl = 'https://example.com/default-avatar.png',
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      bio: json['bio'] as String?,
      avatarUrl: json['avatarUrl'] as String? ?? 'https://example.com/default-avatar.png',
    );
  }
}

json_serializable 패키지 사용

대규모 프로젝트에서는 json_serializable 패키지가 보일러플레이트를 자동화합니다:

단계 1: 의존성 추가

# pubspec.yaml
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  json_serializable: ^6.7.1
  build_runner: ^2.4.6

단계 2: 어노테이션이 있는 모델 생성

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String email;

  @JsonKey(name: 'is_active')
  final bool isActive;

  @JsonKey(defaultValue: 0.0)
  final double score;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.isActive,
    required this.score,
  });

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

단계 3: Build Runner 실행

dart run build_runner build --delete-conflicting-outputs

Freezed로 불변 모델 만들기

freezed 패키지는 copyWith, 동등성, JSON 직렬화가 포함된 불변 클래스를 생성합니다:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    required int id,
    required String name,
    required String email,
    @Default(false) bool isActive,
  }) = _User;

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

일반적인 실수와 해결법

실수해결법
null 체크 없이 `json['key']` 사용`json['key'] as String? ?? ''` 사용 또는 null 처리
중첩 객체 변환을 잊음객체에는 `NestedClass.fromJson(json['key'])` 호출
모든 숫자에 `int` 사용`num` 사용 후 변환: `(json['price'] as num).toDouble()`
누락된 키를 처리하지 않음`json.containsKey('key')` 사용 또는 기본값 제공
toJson에서 잘못된 타입 반환중첩 객체가 자체 `.toJson()` 호출을 확인

자주 묻는 질문

JSON을 Dart 클래스로 변환하는 가장 좋은 방법은?

소규모 프로젝트에서는 수동 변환이 효과적입니다. 대규모 프로젝트에서는 json_serializable 또는 freezed 패키지로 보일러플레이트 코드를 자동 생성합니다.

Dart에서 동적 JSON 키를 어떻게 처리하나요?

알 수 없는 키가 있는 객체에는 Map<String, dynamic>을 사용합니다.

json_serializable과 freezed 중 어떤 것을 사용해야 하나요?

간단한 JSON 직렬화에는 json_serializable. 불변성, copyWith, union 타입도 필요하면 freezed를 사용합니다.

JSON-Dart 변환에서 날짜를 어떻게 처리하나요?

DateTime.parse(json['date'])로 ISO 8601 문자열을 파싱합니다. toJson에서는 dateTime.toIso8601String()을 사용합니다.

도움이 되었나요?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy